solar-forecast-langgraph 0.1.0__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.
- solar_forecast_langgraph-0.1.0/LICENSE +21 -0
- solar_forecast_langgraph-0.1.0/PKG-INFO +300 -0
- solar_forecast_langgraph-0.1.0/README.md +275 -0
- solar_forecast_langgraph-0.1.0/pyproject.toml +70 -0
- solar_forecast_langgraph-0.1.0/setup.cfg +4 -0
- solar_forecast_langgraph-0.1.0/solar_forecast/__init__.py +65 -0
- solar_forecast_langgraph-0.1.0/solar_forecast/config.py +126 -0
- solar_forecast_langgraph-0.1.0/solar_forecast/history.py +252 -0
- solar_forecast_langgraph-0.1.0/solar_forecast/main.py +182 -0
- solar_forecast_langgraph-0.1.0/solar_forecast/model.py +390 -0
- solar_forecast_langgraph-0.1.0/solar_forecast/weather.py +177 -0
- solar_forecast_langgraph-0.1.0/solar_forecast/workflow.py +339 -0
- solar_forecast_langgraph-0.1.0/solar_forecast_langgraph.egg-info/PKG-INFO +300 -0
- solar_forecast_langgraph-0.1.0/solar_forecast_langgraph.egg-info/SOURCES.txt +20 -0
- solar_forecast_langgraph-0.1.0/solar_forecast_langgraph.egg-info/dependency_links.txt +1 -0
- solar_forecast_langgraph-0.1.0/solar_forecast_langgraph.egg-info/entry_points.txt +2 -0
- solar_forecast_langgraph-0.1.0/solar_forecast_langgraph.egg-info/requires.txt +17 -0
- solar_forecast_langgraph-0.1.0/solar_forecast_langgraph.egg-info/top_level.txt +1 -0
- solar_forecast_langgraph-0.1.0/tests/test_config.py +204 -0
- solar_forecast_langgraph-0.1.0/tests/test_model.py +258 -0
- solar_forecast_langgraph-0.1.0/tests/test_weather.py +94 -0
- solar_forecast_langgraph-0.1.0/tests/test_workflow.py +161 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 4alvit
|
|
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,300 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: solar-forecast-langgraph
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: LangGraph workflow for solar forecasting with OpenMeteo, historical data, and inverter-control integration
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: langgraph>=0.2.0
|
|
9
|
+
Requires-Dist: langchain-core>=0.3.0
|
|
10
|
+
Requires-Dist: langchain-openai>=0.2.0
|
|
11
|
+
Requires-Dist: requests>=2.31
|
|
12
|
+
Requires-Dist: pydantic>=2.8
|
|
13
|
+
Requires-Dist: pydantic-settings>=2.3
|
|
14
|
+
Requires-Dist: numpy>=1.26
|
|
15
|
+
Requires-Dist: pandas>=2.2
|
|
16
|
+
Requires-Dist: scikit-learn>=1.5
|
|
17
|
+
Requires-Dist: tenacity>=8.3
|
|
18
|
+
Requires-Dist: python-dotenv>=1.0
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: ruff>=0.5.0; extra == "dev"
|
|
21
|
+
Requires-Dist: pytest>=8.3; extra == "dev"
|
|
22
|
+
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# Solar Forecast LangGraph
|
|
27
|
+
|
|
28
|
+
[](https://github.com/4alvit/solar-forecast-langgraph/actions/workflows/ci.yml)
|
|
29
|
+
[](https://codecov.io/gh/4alvit/solar-forecast-langgraph)
|
|
30
|
+
[](https://pypi.org/project/solar-forecast-langgraph/)
|
|
31
|
+
[](https://pypi.org/project/solar-forecast-langgraph/)
|
|
32
|
+
[](LICENSE)
|
|
33
|
+
|
|
34
|
+
LangGraph workflow for solar forecasting with:
|
|
35
|
+
- **Weather data agent** — OpenMeteo API (free, no key required)
|
|
36
|
+
- **Historical generation loader** — From inverter-monitoring webhook
|
|
37
|
+
- **Panel configuration schema** — Azimuth, tilt, capacity, losses
|
|
38
|
+
- **Forecast model** — Physical (clear-sky + clouds) + Statistical (ML) + LLM reasoning
|
|
39
|
+
- **Inverter-control integration** — Pre-charge battery before cloudy periods
|
|
40
|
+
- **Accuracy tracking** — Feedback loop for continuous improvement
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Architecture
|
|
45
|
+
|
|
46
|
+
```mermaid
|
|
47
|
+
graph TD
|
|
48
|
+
A[User Request] --> B[LangGraph Workflow]
|
|
49
|
+
|
|
50
|
+
B --> C[Fetch Weather<br/>OpenMeteo API]
|
|
51
|
+
B --> D[Fetch History<br/>inverter-monitoring]
|
|
52
|
+
|
|
53
|
+
C --> E[Train Statistical Model]
|
|
54
|
+
D --> E
|
|
55
|
+
|
|
56
|
+
E --> F[Generate Forecast<br/>Physical + Statistical Ensemble]
|
|
57
|
+
F --> G[LLM Enhancement<br/>Weather pattern analysis]
|
|
58
|
+
G --> H[Inverter-Control Hook<br/>Pre-charge decision]
|
|
59
|
+
H --> I[Final Forecast Output]
|
|
60
|
+
|
|
61
|
+
I --> J[Accuracy Tracking<br/>Feedback Loop]
|
|
62
|
+
J -.-> K[Model Retraining - Scheduler]
|
|
63
|
+
|
|
64
|
+
subgraph "Data Sources"
|
|
65
|
+
C
|
|
66
|
+
D
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
subgraph "Models"
|
|
70
|
+
E
|
|
71
|
+
F
|
|
72
|
+
G
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
subgraph "Integrations"
|
|
76
|
+
H
|
|
77
|
+
end
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Installation
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# From PyPI (when published)
|
|
86
|
+
pip install solar-forecast-langgraph
|
|
87
|
+
|
|
88
|
+
# From source
|
|
89
|
+
git clone git@github.com:4alvit/solar-forecast-langgraph.git
|
|
90
|
+
cd solar-forecast-langgraph
|
|
91
|
+
python -m venv .venv
|
|
92
|
+
source .venv/bin/activate
|
|
93
|
+
pip install -e ".[dev]"
|
|
94
|
+
pre-commit install
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Quick Start
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# Copy example config and customize
|
|
103
|
+
cp site_config.example.py site_config.local.py
|
|
104
|
+
# Edit site_config.local.py with your panel details
|
|
105
|
+
|
|
106
|
+
# Run forecast (48h horizon, 30 days history)
|
|
107
|
+
solar-forecast --config site_config.local.py --horizon 48 --output forecast.json
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Output example:
|
|
111
|
+
```
|
|
112
|
+
Forecast Summary:
|
|
113
|
+
Site: my-solar-site
|
|
114
|
+
Panel: south-roof
|
|
115
|
+
Method: ensemble
|
|
116
|
+
Horizon: 48 hours
|
|
117
|
+
Total Energy: 42500 Wh
|
|
118
|
+
Generated: 2024-01-15T10:30:00+00:00
|
|
119
|
+
|
|
120
|
+
Next 12 hours:
|
|
121
|
+
2024-01-15 11:00: 1200W (1200Wh) [960-1440]
|
|
122
|
+
2024-01-15 12:00: 2800W (2800Wh) [2240-3360]
|
|
123
|
+
2024-01-15 13:00: 3500W (3500Wh) [2800-4200]
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Configuration
|
|
129
|
+
|
|
130
|
+
### Panel Configuration
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from solar_forecast.config import PanelConfig, SiteConfig
|
|
134
|
+
|
|
135
|
+
panel = PanelConfig(
|
|
136
|
+
name="South Roof",
|
|
137
|
+
panel_id="south-roof",
|
|
138
|
+
azimuth=180, # 0=N, 90=E, 180=S, 270=W
|
|
139
|
+
tilt=35, # Degrees from horizontal
|
|
140
|
+
capacity_kw=5.0, # DC capacity
|
|
141
|
+
module_count=14,
|
|
142
|
+
latitude=52.37,
|
|
143
|
+
longitude=4.90,
|
|
144
|
+
# Optional losses
|
|
145
|
+
shading_loss=0.0,
|
|
146
|
+
soiling_loss=0.02,
|
|
147
|
+
wiring_loss=0.01,
|
|
148
|
+
module_efficiency=0.20,
|
|
149
|
+
temperature_coefficient=-0.0035,
|
|
150
|
+
inverter_efficiency=0.96,
|
|
151
|
+
dc_ac_ratio=1.2,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
site = SiteConfig(
|
|
155
|
+
site_name="my-site",
|
|
156
|
+
latitude=52.37,
|
|
157
|
+
longitude=4.90,
|
|
158
|
+
timezone="Europe/Amsterdam",
|
|
159
|
+
panels=[panel],
|
|
160
|
+
)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
See [site_config.example.py](site_config.example.py) for full example.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Forecast Methods
|
|
168
|
+
|
|
169
|
+
| Method | Description | Use Case |
|
|
170
|
+
|--------|-------------|----------|
|
|
171
|
+
| `physical` | Clear-sky model + cloud adjustment | No historical data |
|
|
172
|
+
| `statistical` | ML model on historical data | Sufficient history (30+ days) |
|
|
173
|
+
| `ensemble` | Weighted: 60% physical + 40% statistical | **Default, best accuracy** |
|
|
174
|
+
| `llm_enhanced` | LLM analyzes weather patterns | Future: complex weather |
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Inverter-Control Integration
|
|
179
|
+
|
|
180
|
+
The workflow includes a hook for **pre-charging batteries** before forecasted cloudy periods:
|
|
181
|
+
|
|
182
|
+
```mermaid
|
|
183
|
+
sequenceDiagram
|
|
184
|
+
participant WF as LangGraph Workflow
|
|
185
|
+
participant FC as Forecast
|
|
186
|
+
participant IC as Inverter-Control
|
|
187
|
+
|
|
188
|
+
WF->>FC: Generate 48h forecast
|
|
189
|
+
FC->>WF: Forecast points with confidence
|
|
190
|
+
WF->>WF: Analyze next 6h total energy
|
|
191
|
+
alt Low generation (< 5kWh in 6h)
|
|
192
|
+
WF->>IC: POST /api/v1/pre-charge
|
|
193
|
+
IC->>IC: Increase battery target SoC
|
|
194
|
+
IC-->>WF: Pre-charge initiated
|
|
195
|
+
end
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Enable in config:
|
|
199
|
+
```python
|
|
200
|
+
# In site_config.local.py
|
|
201
|
+
INVERTER_CONTROL_URL = "http://inverter-control:8081"
|
|
202
|
+
INVERTER_CONTROL_API_KEY = "your-key"
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Accuracy Tracking
|
|
208
|
+
|
|
209
|
+
Feedback loop for continuous improvement:
|
|
210
|
+
|
|
211
|
+
```mermaid
|
|
212
|
+
graph LR
|
|
213
|
+
A[Forecast] --> B[Actual Generation]
|
|
214
|
+
B --> C[Error Metrics]
|
|
215
|
+
C --> D{Error > Threshold?}
|
|
216
|
+
D -->|Yes| E[Flag for Review]
|
|
217
|
+
D -->|No| F[Update Training Data]
|
|
218
|
+
F --> G[Retrain Model]
|
|
219
|
+
E --> H[Human Analysis]
|
|
220
|
+
H --> G
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Metrics tracked per panel:
|
|
224
|
+
- **MAE** (Mean Absolute Error)
|
|
225
|
+
- **RMSE** (Root Mean Square Error)
|
|
226
|
+
- **MAPE** (Mean Absolute Percentage Error)
|
|
227
|
+
- **Bias** (Systematic over/under prediction)
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Development
|
|
232
|
+
|
|
233
|
+
### Run Tests
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
pytest tests/ -v --cov=solar_forecast
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Lint & Format
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
ruff check .
|
|
243
|
+
ruff format .
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Type Check
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
mypy solar_forecast/
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Project Structure
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
solar-forecast-langgraph/
|
|
258
|
+
├── solar_forecast/
|
|
259
|
+
│ ├── __init__.py # Public exports
|
|
260
|
+
│ ├── config.py # Panel/Site configuration schemas
|
|
261
|
+
│ ├── weather.py # OpenMeteo client
|
|
262
|
+
│ ├── history.py # Historical data loaders
|
|
263
|
+
│ ├── model.py # Physical/Statistical/LLM models
|
|
264
|
+
│ ├── workflow.py # LangGraph workflow
|
|
265
|
+
│ └── main.py # CLI entry point
|
|
266
|
+
├── tests/
|
|
267
|
+
│ ├── test_config.py
|
|
268
|
+
│ ├── test_weather.py
|
|
269
|
+
│ ├── test_model.py
|
|
270
|
+
│ └── test_workflow.py
|
|
271
|
+
├── .github/workflows/ci.yml # CI/CD pipeline
|
|
272
|
+
├── pyproject.toml # Package config
|
|
273
|
+
├── site_config.example.py # Example configuration
|
|
274
|
+
└── README.md
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Roadmap
|
|
280
|
+
|
|
281
|
+
- [ ] LLM-enhanced forecasting (weather pattern analysis)
|
|
282
|
+
- [ ] Automated model retraining scheduler
|
|
283
|
+
- [ ] Prometheus metrics export
|
|
284
|
+
- [ ] Grafana dashboard template
|
|
285
|
+
- [ ] Multi-site support in single workflow
|
|
286
|
+
- [ ] Battery SoC optimization (not just pre-charge)
|
|
287
|
+
- [ ] Shadow modeling from 3D terrain
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Related Projects
|
|
292
|
+
|
|
293
|
+
- **[inverter-monitoring](https://github.com/4alvit/inverter-monitoring)** — Generation data webhook
|
|
294
|
+
- **[inverter-control](https://github.com/4alvit/inverter-control)** — Grid-zero feed-in control
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## License
|
|
299
|
+
|
|
300
|
+
MIT License — see [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# Solar Forecast LangGraph
|
|
2
|
+
|
|
3
|
+
[](https://github.com/4alvit/solar-forecast-langgraph/actions/workflows/ci.yml)
|
|
4
|
+
[](https://codecov.io/gh/4alvit/solar-forecast-langgraph)
|
|
5
|
+
[](https://pypi.org/project/solar-forecast-langgraph/)
|
|
6
|
+
[](https://pypi.org/project/solar-forecast-langgraph/)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
LangGraph workflow for solar forecasting with:
|
|
10
|
+
- **Weather data agent** — OpenMeteo API (free, no key required)
|
|
11
|
+
- **Historical generation loader** — From inverter-monitoring webhook
|
|
12
|
+
- **Panel configuration schema** — Azimuth, tilt, capacity, losses
|
|
13
|
+
- **Forecast model** — Physical (clear-sky + clouds) + Statistical (ML) + LLM reasoning
|
|
14
|
+
- **Inverter-control integration** — Pre-charge battery before cloudy periods
|
|
15
|
+
- **Accuracy tracking** — Feedback loop for continuous improvement
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Architecture
|
|
20
|
+
|
|
21
|
+
```mermaid
|
|
22
|
+
graph TD
|
|
23
|
+
A[User Request] --> B[LangGraph Workflow]
|
|
24
|
+
|
|
25
|
+
B --> C[Fetch Weather<br/>OpenMeteo API]
|
|
26
|
+
B --> D[Fetch History<br/>inverter-monitoring]
|
|
27
|
+
|
|
28
|
+
C --> E[Train Statistical Model]
|
|
29
|
+
D --> E
|
|
30
|
+
|
|
31
|
+
E --> F[Generate Forecast<br/>Physical + Statistical Ensemble]
|
|
32
|
+
F --> G[LLM Enhancement<br/>Weather pattern analysis]
|
|
33
|
+
G --> H[Inverter-Control Hook<br/>Pre-charge decision]
|
|
34
|
+
H --> I[Final Forecast Output]
|
|
35
|
+
|
|
36
|
+
I --> J[Accuracy Tracking<br/>Feedback Loop]
|
|
37
|
+
J -.-> K[Model Retraining - Scheduler]
|
|
38
|
+
|
|
39
|
+
subgraph "Data Sources"
|
|
40
|
+
C
|
|
41
|
+
D
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
subgraph "Models"
|
|
45
|
+
E
|
|
46
|
+
F
|
|
47
|
+
G
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
subgraph "Integrations"
|
|
51
|
+
H
|
|
52
|
+
end
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# From PyPI (when published)
|
|
61
|
+
pip install solar-forecast-langgraph
|
|
62
|
+
|
|
63
|
+
# From source
|
|
64
|
+
git clone git@github.com:4alvit/solar-forecast-langgraph.git
|
|
65
|
+
cd solar-forecast-langgraph
|
|
66
|
+
python -m venv .venv
|
|
67
|
+
source .venv/bin/activate
|
|
68
|
+
pip install -e ".[dev]"
|
|
69
|
+
pre-commit install
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Quick Start
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Copy example config and customize
|
|
78
|
+
cp site_config.example.py site_config.local.py
|
|
79
|
+
# Edit site_config.local.py with your panel details
|
|
80
|
+
|
|
81
|
+
# Run forecast (48h horizon, 30 days history)
|
|
82
|
+
solar-forecast --config site_config.local.py --horizon 48 --output forecast.json
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Output example:
|
|
86
|
+
```
|
|
87
|
+
Forecast Summary:
|
|
88
|
+
Site: my-solar-site
|
|
89
|
+
Panel: south-roof
|
|
90
|
+
Method: ensemble
|
|
91
|
+
Horizon: 48 hours
|
|
92
|
+
Total Energy: 42500 Wh
|
|
93
|
+
Generated: 2024-01-15T10:30:00+00:00
|
|
94
|
+
|
|
95
|
+
Next 12 hours:
|
|
96
|
+
2024-01-15 11:00: 1200W (1200Wh) [960-1440]
|
|
97
|
+
2024-01-15 12:00: 2800W (2800Wh) [2240-3360]
|
|
98
|
+
2024-01-15 13:00: 3500W (3500Wh) [2800-4200]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Configuration
|
|
104
|
+
|
|
105
|
+
### Panel Configuration
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from solar_forecast.config import PanelConfig, SiteConfig
|
|
109
|
+
|
|
110
|
+
panel = PanelConfig(
|
|
111
|
+
name="South Roof",
|
|
112
|
+
panel_id="south-roof",
|
|
113
|
+
azimuth=180, # 0=N, 90=E, 180=S, 270=W
|
|
114
|
+
tilt=35, # Degrees from horizontal
|
|
115
|
+
capacity_kw=5.0, # DC capacity
|
|
116
|
+
module_count=14,
|
|
117
|
+
latitude=52.37,
|
|
118
|
+
longitude=4.90,
|
|
119
|
+
# Optional losses
|
|
120
|
+
shading_loss=0.0,
|
|
121
|
+
soiling_loss=0.02,
|
|
122
|
+
wiring_loss=0.01,
|
|
123
|
+
module_efficiency=0.20,
|
|
124
|
+
temperature_coefficient=-0.0035,
|
|
125
|
+
inverter_efficiency=0.96,
|
|
126
|
+
dc_ac_ratio=1.2,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
site = SiteConfig(
|
|
130
|
+
site_name="my-site",
|
|
131
|
+
latitude=52.37,
|
|
132
|
+
longitude=4.90,
|
|
133
|
+
timezone="Europe/Amsterdam",
|
|
134
|
+
panels=[panel],
|
|
135
|
+
)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
See [site_config.example.py](site_config.example.py) for full example.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Forecast Methods
|
|
143
|
+
|
|
144
|
+
| Method | Description | Use Case |
|
|
145
|
+
|--------|-------------|----------|
|
|
146
|
+
| `physical` | Clear-sky model + cloud adjustment | No historical data |
|
|
147
|
+
| `statistical` | ML model on historical data | Sufficient history (30+ days) |
|
|
148
|
+
| `ensemble` | Weighted: 60% physical + 40% statistical | **Default, best accuracy** |
|
|
149
|
+
| `llm_enhanced` | LLM analyzes weather patterns | Future: complex weather |
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Inverter-Control Integration
|
|
154
|
+
|
|
155
|
+
The workflow includes a hook for **pre-charging batteries** before forecasted cloudy periods:
|
|
156
|
+
|
|
157
|
+
```mermaid
|
|
158
|
+
sequenceDiagram
|
|
159
|
+
participant WF as LangGraph Workflow
|
|
160
|
+
participant FC as Forecast
|
|
161
|
+
participant IC as Inverter-Control
|
|
162
|
+
|
|
163
|
+
WF->>FC: Generate 48h forecast
|
|
164
|
+
FC->>WF: Forecast points with confidence
|
|
165
|
+
WF->>WF: Analyze next 6h total energy
|
|
166
|
+
alt Low generation (< 5kWh in 6h)
|
|
167
|
+
WF->>IC: POST /api/v1/pre-charge
|
|
168
|
+
IC->>IC: Increase battery target SoC
|
|
169
|
+
IC-->>WF: Pre-charge initiated
|
|
170
|
+
end
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Enable in config:
|
|
174
|
+
```python
|
|
175
|
+
# In site_config.local.py
|
|
176
|
+
INVERTER_CONTROL_URL = "http://inverter-control:8081"
|
|
177
|
+
INVERTER_CONTROL_API_KEY = "your-key"
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Accuracy Tracking
|
|
183
|
+
|
|
184
|
+
Feedback loop for continuous improvement:
|
|
185
|
+
|
|
186
|
+
```mermaid
|
|
187
|
+
graph LR
|
|
188
|
+
A[Forecast] --> B[Actual Generation]
|
|
189
|
+
B --> C[Error Metrics]
|
|
190
|
+
C --> D{Error > Threshold?}
|
|
191
|
+
D -->|Yes| E[Flag for Review]
|
|
192
|
+
D -->|No| F[Update Training Data]
|
|
193
|
+
F --> G[Retrain Model]
|
|
194
|
+
E --> H[Human Analysis]
|
|
195
|
+
H --> G
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Metrics tracked per panel:
|
|
199
|
+
- **MAE** (Mean Absolute Error)
|
|
200
|
+
- **RMSE** (Root Mean Square Error)
|
|
201
|
+
- **MAPE** (Mean Absolute Percentage Error)
|
|
202
|
+
- **Bias** (Systematic over/under prediction)
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Development
|
|
207
|
+
|
|
208
|
+
### Run Tests
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
pytest tests/ -v --cov=solar_forecast
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Lint & Format
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
ruff check .
|
|
218
|
+
ruff format .
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Type Check
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
mypy solar_forecast/
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Project Structure
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
solar-forecast-langgraph/
|
|
233
|
+
├── solar_forecast/
|
|
234
|
+
│ ├── __init__.py # Public exports
|
|
235
|
+
│ ├── config.py # Panel/Site configuration schemas
|
|
236
|
+
│ ├── weather.py # OpenMeteo client
|
|
237
|
+
│ ├── history.py # Historical data loaders
|
|
238
|
+
│ ├── model.py # Physical/Statistical/LLM models
|
|
239
|
+
│ ├── workflow.py # LangGraph workflow
|
|
240
|
+
│ └── main.py # CLI entry point
|
|
241
|
+
├── tests/
|
|
242
|
+
│ ├── test_config.py
|
|
243
|
+
│ ├── test_weather.py
|
|
244
|
+
│ ├── test_model.py
|
|
245
|
+
│ └── test_workflow.py
|
|
246
|
+
├── .github/workflows/ci.yml # CI/CD pipeline
|
|
247
|
+
├── pyproject.toml # Package config
|
|
248
|
+
├── site_config.example.py # Example configuration
|
|
249
|
+
└── README.md
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Roadmap
|
|
255
|
+
|
|
256
|
+
- [ ] LLM-enhanced forecasting (weather pattern analysis)
|
|
257
|
+
- [ ] Automated model retraining scheduler
|
|
258
|
+
- [ ] Prometheus metrics export
|
|
259
|
+
- [ ] Grafana dashboard template
|
|
260
|
+
- [ ] Multi-site support in single workflow
|
|
261
|
+
- [ ] Battery SoC optimization (not just pre-charge)
|
|
262
|
+
- [ ] Shadow modeling from 3D terrain
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Related Projects
|
|
267
|
+
|
|
268
|
+
- **[inverter-monitoring](https://github.com/4alvit/inverter-monitoring)** — Generation data webhook
|
|
269
|
+
- **[inverter-control](https://github.com/4alvit/inverter-control)** — Grid-zero feed-in control
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## License
|
|
274
|
+
|
|
275
|
+
MIT License — see [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "solar-forecast-langgraph"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "LangGraph workflow for solar forecasting with OpenMeteo, historical data, and inverter-control integration"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"langgraph>=0.2.0",
|
|
13
|
+
"langchain-core>=0.3.0",
|
|
14
|
+
"langchain-openai>=0.2.0",
|
|
15
|
+
"requests>=2.31",
|
|
16
|
+
"pydantic>=2.8",
|
|
17
|
+
"pydantic-settings>=2.3",
|
|
18
|
+
"numpy>=1.26",
|
|
19
|
+
"pandas>=2.2",
|
|
20
|
+
"scikit-learn>=1.5",
|
|
21
|
+
"tenacity>=8.3",
|
|
22
|
+
"python-dotenv>=1.0",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
dev = [
|
|
27
|
+
"ruff>=0.5.0",
|
|
28
|
+
"pytest>=8.3",
|
|
29
|
+
"pytest-cov>=5.0",
|
|
30
|
+
"pytest-asyncio>=0.23",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
solar-forecast = "solar_forecast.main:main"
|
|
35
|
+
|
|
36
|
+
[tool.setuptools.packages.find]
|
|
37
|
+
where = ["."]
|
|
38
|
+
include = ["solar_forecast*"]
|
|
39
|
+
|
|
40
|
+
[tool.ruff]
|
|
41
|
+
line-length = 100
|
|
42
|
+
target-version = "py311"
|
|
43
|
+
|
|
44
|
+
[tool.ruff.lint]
|
|
45
|
+
ignore = [
|
|
46
|
+
"E722", # bare except
|
|
47
|
+
"F841", # unused variable
|
|
48
|
+
"BLE001", # blind exception
|
|
49
|
+
"S110", # try-except-pass
|
|
50
|
+
"TRY401", # redundant exception in logging
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[tool.pytest.ini_options]
|
|
54
|
+
asyncio_mode = "auto"
|
|
55
|
+
testpaths = ["tests"]
|
|
56
|
+
python_files = "test_*.py"
|
|
57
|
+
python_functions = "test_*"
|
|
58
|
+
|
|
59
|
+
[tool.coverage.run]
|
|
60
|
+
source = ["solar_forecast"]
|
|
61
|
+
omit = ["tests/*", "*/__pycache__/*"]
|
|
62
|
+
|
|
63
|
+
[tool.coverage.report]
|
|
64
|
+
exclude_lines = [
|
|
65
|
+
"pragma: no cover",
|
|
66
|
+
"def __repr__",
|
|
67
|
+
"raise AssertionError",
|
|
68
|
+
"raise NotImplementedError",
|
|
69
|
+
"if __name__ == .__main__.:",
|
|
70
|
+
]
|