windguru 0.2.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.
Files changed (55) hide show
  1. windguru-0.2.0/.github/workflows/ci.yml +26 -0
  2. windguru-0.2.0/.gitignore +30 -0
  3. windguru-0.2.0/AGENTS.md +82 -0
  4. windguru-0.2.0/LICENSE +21 -0
  5. windguru-0.2.0/PKG-INFO +176 -0
  6. windguru-0.2.0/README.md +138 -0
  7. windguru-0.2.0/docs/WIRE.md +103 -0
  8. windguru-0.2.0/docs/code_quality.md +149 -0
  9. windguru-0.2.0/docs/data_engineering_standards.md +247 -0
  10. windguru-0.2.0/docs/mcp.md +71 -0
  11. windguru-0.2.0/examples/agent_session.sh +8 -0
  12. windguru-0.2.0/fixtures/forecast_201_gfs.json +3580 -0
  13. windguru-0.2.0/fixtures/forecast_spot_201.json +1366 -0
  14. windguru-0.2.0/fixtures/forecast_spot_48309.json +1696 -0
  15. windguru-0.2.0/fixtures/model_info_full.json +2656 -0
  16. windguru-0.2.0/fixtures/search_castelldefels.json +144 -0
  17. windguru-0.2.0/fixtures/spots_simplemap_nl.json +1728 -0
  18. windguru-0.2.0/guru/__init__.py +28 -0
  19. windguru-0.2.0/guru/cli/__init__.py +3 -0
  20. windguru-0.2.0/guru/cli/console.py +3 -0
  21. windguru-0.2.0/guru/cli/errors.py +34 -0
  22. windguru-0.2.0/guru/cli/main.py +209 -0
  23. windguru-0.2.0/guru/cli/render.py +88 -0
  24. windguru-0.2.0/guru/core/__init__.py +22 -0
  25. windguru-0.2.0/guru/core/envelope.py +45 -0
  26. windguru-0.2.0/guru/core/errors.py +60 -0
  27. windguru-0.2.0/guru/core/instruct.py +66 -0
  28. windguru-0.2.0/guru/mcp/__init__.py +1 -0
  29. windguru-0.2.0/guru/mcp/_entry.py +31 -0
  30. windguru-0.2.0/guru/mcp/server.py +111 -0
  31. windguru-0.2.0/guru/models/__init__.py +4 -0
  32. windguru-0.2.0/guru/models/aliases.py +54 -0
  33. windguru-0.2.0/guru/models/blend.py +28 -0
  34. windguru-0.2.0/guru/models/forecast.py +62 -0
  35. windguru-0.2.0/guru/models/models.py +5 -0
  36. windguru-0.2.0/guru/search/__init__.py +13 -0
  37. windguru-0.2.0/guru/search/blend.py +101 -0
  38. windguru-0.2.0/guru/search/blend_math.py +128 -0
  39. windguru-0.2.0/guru/search/client.py +62 -0
  40. windguru-0.2.0/guru/search/exceptions.py +34 -0
  41. windguru-0.2.0/guru/search/forecast.py +106 -0
  42. windguru-0.2.0/guru/search/near.py +94 -0
  43. windguru-0.2.0/guru/search/spots.py +102 -0
  44. windguru-0.2.0/pyproject.toml +70 -0
  45. windguru-0.2.0/scripts/capture_fixtures.py +65 -0
  46. windguru-0.2.0/skills/guru/SKILL.md +49 -0
  47. windguru-0.2.0/tests/conftest.py +14 -0
  48. windguru-0.2.0/tests/test_blend.py +36 -0
  49. windguru-0.2.0/tests/test_cli_json.py +62 -0
  50. windguru-0.2.0/tests/test_forecast_decode.py +45 -0
  51. windguru-0.2.0/tests/test_live.py +37 -0
  52. windguru-0.2.0/tests/test_mcp.py +33 -0
  53. windguru-0.2.0/tests/test_models.py +13 -0
  54. windguru-0.2.0/tests/test_near.py +16 -0
  55. windguru-0.2.0/tests/test_resolve.py +76 -0
@@ -0,0 +1,26 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.10", "3.12"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: ${{ matrix.python-version }}
19
+ - name: Install
20
+ run: |
21
+ python -m pip install -U pip
22
+ pip install -e ".[dev,mcp]"
23
+ - name: Lint
24
+ run: ruff check .
25
+ - name: Test
26
+ run: pytest -q -m "not live"
@@ -0,0 +1,30 @@
1
+ # Byte-compiled / cache
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ .eggs/
7
+ dist/
8
+ build/
9
+ .pytest_cache/
10
+ .ruff_cache/
11
+ .mypy_cache/
12
+ .coverage
13
+ htmlcov/
14
+
15
+ # Env / secrets
16
+ .env
17
+ .env.*
18
+ !.env.example
19
+ *.pem
20
+
21
+ # Editors / OS
22
+ .venv/
23
+ venv/
24
+ .idea/
25
+ .vscode/
26
+ .DS_Store
27
+
28
+ # Local capture dumps (keep fixtures/ tracked intentionally)
29
+ /tmp/
30
+ *.har
@@ -0,0 +1,82 @@
1
+ # AGENTS.md — how to extend `guru`
2
+
3
+ You are working on **guru**: a Windguru CLI/library/MCP reverse-engineered the same way [`fli`](https://github.com/punitarani/fli) reverse-engineers Google Flights.
4
+
5
+ ## Calling guru from an agent
6
+
7
+ ```bash
8
+ pipx install 'windguru[mcp]'
9
+ guru instruct --json
10
+ guru spots "<place>" --json # or: guru near --lat --lon --json
11
+ guru best <id_spot> --json # WINDGURU_DEFAULT → top 3 forecasts
12
+ ```
13
+
14
+ - Prefer **`best` / `best_forecast`**, not raw GFS.
15
+ - Preset is always **WINDGURU_DEFAULT** (only adaptive Tune preset we support).
16
+ - On `error_type: ambiguous`, pick from `candidates` — never silent first-match.
17
+ - Envelope: `ok`, `api_version`, `error_type`, `retryable` (shared CLI + MCP).
18
+ - Do **not** scrape HTML, Tune jBox, or MapLibre. Do **not** require PRO.
19
+
20
+ ## Hard rule: clone methods from fli
21
+
22
+ **Strongly prefer copying patterns from fli over inventing new ones.**
23
+
24
+ Keep a local checkout of fli (`git clone https://github.com/punitarani/fli /tmp/fli-ref`) and **read their search client before changing ours**.
25
+
26
+ | fli | guru | Copy this idea |
27
+ |-----|------|----------------|
28
+ | `fli/search/client.py` | `guru/search/client.py` | `curl_cffi` impersonation, retries, env timeouts |
29
+ | `fli/core/errors.py` | `guru/core/errors.py` | Shared CLI/MCP `error_type` vocabulary |
30
+ | capture scripts + fixtures | `scripts/capture_fixtures.py` + `fixtures/` | Browser Network → fixture → offline parser tests |
31
+ | `fli/cli/` | `guru/cli/` | Typer + Rich + `--json` |
32
+ | `fli/mcp/` | `guru/mcp/` | FastMCP STDIO + HTTP |
33
+ | `fli/models/` | `guru/models/` | Pydantic only — no I/O |
34
+ | `_tfs` migration when signed RPC broke | `docs/WIRE.md` SPA `rundef` form | When simple `forecast` dies, use page-derived params |
35
+
36
+ ## Reverse-engineering workflow (required)
37
+
38
+ 1. Open the spot in **Cursor browser** (`https://www.windguru.cz/201`).
39
+ 2. CDP / Network: list requests matching `iapi.php`.
40
+ 3. Note host (`.cz` vs `.net`), `q=`, and params (`id_spot`, `id_model`, `rundef`, `opt=simplemap`, …).
41
+ 4. Reproduce with `curl_cffi` + **Referer**.
42
+ 5. Dump fixture → parser test → update `docs/WIRE.md`.
43
+
44
+ **Never HTML-scrape the forecast table or Tune UI** as the primary path.
45
+
46
+ ## Product shape
47
+
48
+ ```text
49
+ pipx install windguru
50
+ guru spots <query>
51
+ guru near --lat Y --lon X
52
+ guru best <id|name> [--top 3] [--hours N] [--json]
53
+ guru-mcp
54
+ ```
55
+
56
+ ## Do / don’t
57
+
58
+ - **Do** extend `MODELS` from live Network captures.
59
+ - **Do** keep free vs PRO failures explicit (PRO is out of core).
60
+ - **Do** stay polite on rate limits.
61
+ - **Don’t** commit cookies / PRO passwords / private nicknames as defaults.
62
+ - **Don’t** ship a worldwide spot dump in the wheel (live `near` / `spots` only).
63
+ - **Don’t** depend on `life-research` — this repo is public and clean-slate.
64
+
65
+ ## Engineering standards
66
+
67
+ Keep and follow (do not delete):
68
+
69
+ - [`docs/code_quality.md`](docs/code_quality.md) — Korotkevich / Tourist bar
70
+ - [`docs/data_engineering_standards.md`](docs/data_engineering_standards.md) — Gray / Stonebraker bar
71
+
72
+ ## Dev commands
73
+
74
+ ```bash
75
+ pipx install -e ".[dev,mcp]"
76
+ # or: source .venv/bin/activate && pip install -e ".[dev,mcp]"
77
+ guru spots "foster city"
78
+ guru best 201 -H 12 --json
79
+ pytest -q
80
+ pytest -m live
81
+ ruff check .
82
+ ```
windguru-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Berend Gort
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,176 @@
1
+ Metadata-Version: 2.5
2
+ Name: windguru
3
+ Version: 0.2.0
4
+ Summary: Windguru CLI, MCP server, and Python library — reverse-engineered forecast access (fli-style)
5
+ Project-URL: Homepage, https://github.com/berendgort/guru
6
+ Project-URL: Repository, https://github.com/berendgort/guru
7
+ Project-URL: Issues, https://github.com/berendgort/guru/issues
8
+ Author-email: Berend Gort <berend.gort@gmail.com>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: cli,forecast,kitesurf,mcp,weather,wind,windguru
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: curl-cffi>=0.7.0
22
+ Requires-Dist: pydantic>=2.10.0
23
+ Requires-Dist: rich>=13.8.0
24
+ Requires-Dist: tenacity>=9.0.0
25
+ Requires-Dist: typer>=0.15.1
26
+ Provides-Extra: all
27
+ Requires-Dist: fastmcp>=3.2; extra == 'all'
28
+ Requires-Dist: mcp>=1.2.0; extra == 'all'
29
+ Provides-Extra: dev
30
+ Requires-Dist: build>=1.2.0; extra == 'dev'
31
+ Requires-Dist: pytest>=8.3.4; extra == 'dev'
32
+ Requires-Dist: ruff>=0.8.4; extra == 'dev'
33
+ Requires-Dist: twine>=6.0.0; extra == 'dev'
34
+ Provides-Extra: mcp
35
+ Requires-Dist: fastmcp>=3.2; extra == 'mcp'
36
+ Requires-Dist: mcp>=1.2.0; extra == 'mcp'
37
+ Description-Content-Type: text/markdown
38
+
39
+ # guru
40
+
41
+ Windguru **CLI + MCP + Python library**. Same idea as [`fli`](https://github.com/punitarani/fli): reverse-engineered JSON, zero HTML scraping, agent-first.
42
+
43
+ > Free only — named spots + WINDGURU DEFAULT Tune top-3. No PRO lat/lon click-forecast.
44
+
45
+ ## Install
46
+
47
+ ```bash
48
+ # CLI (recommended)
49
+ pipx install windguru
50
+
51
+ # CLI + MCP server
52
+ pipx install 'windguru[mcp]'
53
+
54
+ guru --help
55
+ guru instruct --json
56
+ ```
57
+
58
+ Until the PyPI release is live, install from GitHub:
59
+
60
+ ```bash
61
+ pipx install 'windguru @ git+https://github.com/berendgort/guru.git'
62
+ pipx install 'windguru[mcp] @ git+https://github.com/berendgort/guru.git'
63
+ ```
64
+
65
+ PyPI name **`windguru`**, command **`guru`**, MCP **`guru-mcp`**.
66
+
67
+ Or with pip:
68
+
69
+ ```bash
70
+ pip install windguru
71
+ pip install 'windguru[mcp]'
72
+ ```
73
+
74
+ ## MCP Server
75
+
76
+ ```bash
77
+ pipx install 'windguru[mcp]'
78
+
79
+ # STDIO (Cursor / Claude Desktop)
80
+ guru-mcp
81
+
82
+ # HTTP (streamable)
83
+ guru-mcp-http # http://127.0.0.1:8000/mcp/
84
+ ```
85
+
86
+ ### Connecting to Claude Desktop / Cursor
87
+
88
+ ```json
89
+ {
90
+ "mcpServers": {
91
+ "guru": {
92
+ "command": "guru-mcp"
93
+ }
94
+ }
95
+ }
96
+ ```
97
+
98
+ > Tip: if the binary is not on PATH, use the full path from `which guru-mcp`
99
+ > (often `~/.local/bin/guru-mcp`).
100
+
101
+ ### MCP tools
102
+
103
+ | Tool | Description |
104
+ |------|-------------|
105
+ | `instruct` | Agent recipe (WINDGURU_DEFAULT → top 3) |
106
+ | `search_spots` | Named spot search |
107
+ | `near_spots` | Free map markers near lat/lon |
108
+ | `resolve_spot` | Name/id → spot (`ambiguous` + candidates) |
109
+ | `best_forecast` | Tune weights → top models → forecasts |
110
+ | `get_forecast` | Single-model escape hatch |
111
+ | `list_models` | Known aliases |
112
+
113
+ ## Agent recipe
114
+
115
+ ```bash
116
+ guru instruct --json
117
+ guru spots "castelldefels" --json
118
+ guru best 201 --json
119
+ guru near --lat 51.9 --lon 4.1 --json
120
+ guru best 48309 -H 24 --json # De Slufter
121
+ ```
122
+
123
+ `guru best` always uses **WINDGURU_DEFAULT** Tune weights and returns the **top 3** models + forecasts. Prefer that over raw GFS.
124
+
125
+ JSON envelope: `{"ok": true|false, "api_version": 1, "data"|error fields}`. Shared `error_type` / `retryable` with MCP.
126
+
127
+ ## CLI
128
+
129
+ | Command | Role |
130
+ |---------|------|
131
+ | `guru instruct` | Teach agents the workflow |
132
+ | `guru spots <q>` | Name search |
133
+ | `guru near --lat --lon` | Free map markers near a point |
134
+ | `guru best <spot>` | WINDGURU_DEFAULT → top 3 → forecasts |
135
+ | `guru forecast <spot> -m gfs` | Single model |
136
+ | `guru models` / `schema` / `doctor` | Discoverability |
137
+
138
+ Ambiguous names fail with `error_type: ambiguous` + `candidates` (pass numeric id or `--pick`).
139
+
140
+ ## Library
141
+
142
+ ```python
143
+ from guru import search_spots, get_best_forecast, spots_near
144
+
145
+ spots = search_spots("castelldefels")
146
+ best = get_best_forecast(201, top=3, hours=24)
147
+ print(best.models[0].name, best.models[0].weight_pct)
148
+ print(best.forecasts[0].hours[0].wind_kn)
149
+ ```
150
+
151
+ ## Architecture
152
+
153
+ | Layer | Path | Role |
154
+ |-------|------|------|
155
+ | Core | `guru/core/` | Shared envelope, errors, instruct recipe |
156
+ | CLI | `guru/cli/` | Typer + Rich + `--json` (thin over core) |
157
+ | MCP | `guru/mcp/` | FastMCP over core (no CLI imports) |
158
+ | Search | `guru/search/` | HTTP (`curl_cffi`), blend_math, near, forecast |
159
+ | Models | `guru/models/` | Pydantic + aliases only |
160
+ | Wire | [`docs/WIRE.md`](docs/WIRE.md) | Captured `iapi.php` |
161
+ | MCP | [`docs/mcp.md`](docs/mcp.md) | `guru-mcp` setup + tools |
162
+
163
+ Read [`AGENTS.md`](AGENTS.md) before extending. Capture Network → fixtures → tests.
164
+
165
+ Engineering standards (kept in-repo):
166
+
167
+ - [`docs/code_quality.md`](docs/code_quality.md) — Korotkevich / Tourist bar (layers, size caps, pure core)
168
+ - [`docs/data_engineering_standards.md`](docs/data_engineering_standards.md) — Gray / Stonebraker bar (one writer, clocks, contracts)
169
+
170
+ ## Disclaimer
171
+
172
+ Unofficial. Not affiliated with Windguru. Personal / research use; respect ToS and rate limits.
173
+
174
+ ## License
175
+
176
+ MIT
@@ -0,0 +1,138 @@
1
+ # guru
2
+
3
+ Windguru **CLI + MCP + Python library**. Same idea as [`fli`](https://github.com/punitarani/fli): reverse-engineered JSON, zero HTML scraping, agent-first.
4
+
5
+ > Free only — named spots + WINDGURU DEFAULT Tune top-3. No PRO lat/lon click-forecast.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ # CLI (recommended)
11
+ pipx install windguru
12
+
13
+ # CLI + MCP server
14
+ pipx install 'windguru[mcp]'
15
+
16
+ guru --help
17
+ guru instruct --json
18
+ ```
19
+
20
+ Until the PyPI release is live, install from GitHub:
21
+
22
+ ```bash
23
+ pipx install 'windguru @ git+https://github.com/berendgort/guru.git'
24
+ pipx install 'windguru[mcp] @ git+https://github.com/berendgort/guru.git'
25
+ ```
26
+
27
+ PyPI name **`windguru`**, command **`guru`**, MCP **`guru-mcp`**.
28
+
29
+ Or with pip:
30
+
31
+ ```bash
32
+ pip install windguru
33
+ pip install 'windguru[mcp]'
34
+ ```
35
+
36
+ ## MCP Server
37
+
38
+ ```bash
39
+ pipx install 'windguru[mcp]'
40
+
41
+ # STDIO (Cursor / Claude Desktop)
42
+ guru-mcp
43
+
44
+ # HTTP (streamable)
45
+ guru-mcp-http # http://127.0.0.1:8000/mcp/
46
+ ```
47
+
48
+ ### Connecting to Claude Desktop / Cursor
49
+
50
+ ```json
51
+ {
52
+ "mcpServers": {
53
+ "guru": {
54
+ "command": "guru-mcp"
55
+ }
56
+ }
57
+ }
58
+ ```
59
+
60
+ > Tip: if the binary is not on PATH, use the full path from `which guru-mcp`
61
+ > (often `~/.local/bin/guru-mcp`).
62
+
63
+ ### MCP tools
64
+
65
+ | Tool | Description |
66
+ |------|-------------|
67
+ | `instruct` | Agent recipe (WINDGURU_DEFAULT → top 3) |
68
+ | `search_spots` | Named spot search |
69
+ | `near_spots` | Free map markers near lat/lon |
70
+ | `resolve_spot` | Name/id → spot (`ambiguous` + candidates) |
71
+ | `best_forecast` | Tune weights → top models → forecasts |
72
+ | `get_forecast` | Single-model escape hatch |
73
+ | `list_models` | Known aliases |
74
+
75
+ ## Agent recipe
76
+
77
+ ```bash
78
+ guru instruct --json
79
+ guru spots "castelldefels" --json
80
+ guru best 201 --json
81
+ guru near --lat 51.9 --lon 4.1 --json
82
+ guru best 48309 -H 24 --json # De Slufter
83
+ ```
84
+
85
+ `guru best` always uses **WINDGURU_DEFAULT** Tune weights and returns the **top 3** models + forecasts. Prefer that over raw GFS.
86
+
87
+ JSON envelope: `{"ok": true|false, "api_version": 1, "data"|error fields}`. Shared `error_type` / `retryable` with MCP.
88
+
89
+ ## CLI
90
+
91
+ | Command | Role |
92
+ |---------|------|
93
+ | `guru instruct` | Teach agents the workflow |
94
+ | `guru spots <q>` | Name search |
95
+ | `guru near --lat --lon` | Free map markers near a point |
96
+ | `guru best <spot>` | WINDGURU_DEFAULT → top 3 → forecasts |
97
+ | `guru forecast <spot> -m gfs` | Single model |
98
+ | `guru models` / `schema` / `doctor` | Discoverability |
99
+
100
+ Ambiguous names fail with `error_type: ambiguous` + `candidates` (pass numeric id or `--pick`).
101
+
102
+ ## Library
103
+
104
+ ```python
105
+ from guru import search_spots, get_best_forecast, spots_near
106
+
107
+ spots = search_spots("castelldefels")
108
+ best = get_best_forecast(201, top=3, hours=24)
109
+ print(best.models[0].name, best.models[0].weight_pct)
110
+ print(best.forecasts[0].hours[0].wind_kn)
111
+ ```
112
+
113
+ ## Architecture
114
+
115
+ | Layer | Path | Role |
116
+ |-------|------|------|
117
+ | Core | `guru/core/` | Shared envelope, errors, instruct recipe |
118
+ | CLI | `guru/cli/` | Typer + Rich + `--json` (thin over core) |
119
+ | MCP | `guru/mcp/` | FastMCP over core (no CLI imports) |
120
+ | Search | `guru/search/` | HTTP (`curl_cffi`), blend_math, near, forecast |
121
+ | Models | `guru/models/` | Pydantic + aliases only |
122
+ | Wire | [`docs/WIRE.md`](docs/WIRE.md) | Captured `iapi.php` |
123
+ | MCP | [`docs/mcp.md`](docs/mcp.md) | `guru-mcp` setup + tools |
124
+
125
+ Read [`AGENTS.md`](AGENTS.md) before extending. Capture Network → fixtures → tests.
126
+
127
+ Engineering standards (kept in-repo):
128
+
129
+ - [`docs/code_quality.md`](docs/code_quality.md) — Korotkevich / Tourist bar (layers, size caps, pure core)
130
+ - [`docs/data_engineering_standards.md`](docs/data_engineering_standards.md) — Gray / Stonebraker bar (one writer, clocks, contracts)
131
+
132
+ ## Disclaimer
133
+
134
+ Unofficial. Not affiliated with Windguru. Personal / research use; respect ToS and rate limits.
135
+
136
+ ## License
137
+
138
+ MIT
@@ -0,0 +1,103 @@
1
+ # Wire — Windguru `iapi.php`
2
+
3
+ Public JSON the SPA uses. Drive these endpoints with `curl_cffi` + **Referer**. Never scrape HTML, Tune jBox, or MapLibre.
4
+
5
+ Captured 2026-09-22 (Castelldefels `201`, De Slufter `48309`). Fixtures under `fixtures/`.
6
+
7
+ ## Hosts
8
+
9
+ | Base | Use |
10
+ |------|-----|
11
+ | `https://www.windguru.cz/int/iapi.php` | Search, simplemap, `forecast_spot`, `forecast`, `model_info_full` |
12
+ | `https://www.windguru.net/int/iapi.php` | SPA `forecast` with `rundef` + `cache_index` (fallback) |
13
+
14
+ | Call | Referer |
15
+ |------|---------|
16
+ | search / model_info | `https://www.windguru.cz/` |
17
+ | spot / forecast | `https://www.windguru.cz/{id_spot}` |
18
+ | simplemap | `https://www.windguru.cz/map/spot/` |
19
+
20
+ ## Queries (free)
21
+
22
+ ### `q=search_spots&search=…`
23
+
24
+ ```json
25
+ { "count": N, "spots": [{ "id_spot", "spotname", "country", "nickname?" }] }
26
+ ```
27
+
28
+ Name search only — no lat/lon. CLI: `guru spots`.
29
+
30
+ ### `q=spots&opt=simplemap&WGCACHEABLE=1800`
31
+
32
+ Same markers as the [spot map](https://www.windguru.cz/map/spot/):
33
+
34
+ ```json
35
+ { "count": N, "spots": [[id_spot, spotname, lat, lon], ...] }
36
+ ```
37
+
38
+ ~10k rows worldwide. CLI: `guru near --lat --lon`. No PRO.
39
+
40
+ ### `q=forecast_spot&id_spot=…`
41
+
42
+ | Path | Content |
43
+ |------|---------|
44
+ | `spots["{id}"]` | name, lat/lon/alt, **`models`** (id_model list for that spot) |
45
+ | `tabs[0].blend` | **WINDGURU DEFAULT** (`id_blend_settings: 1`): `res_sensitivity`, `init_sensitivity`, `model_koef` |
46
+ | `tabs[0].id_model_arr` | per-model `rundef`, `cache_index`, `initstr` |
47
+
48
+ ### `q=model_info_full`
49
+
50
+ Keyed by id string. Fields used by blend: `model_name`, `resolution`, `initstamp`, `priority`, `wave`, `virtual`.
51
+
52
+ ### `q=forecast&id_spot=…&id_model=…`
53
+
54
+ Top-level + `fcst` parallel arrays (**knots**): `hours`, `WINDSPD`, `GUST`, `WINDDIR`, `TMP`/`TMPE`, `APCP`/`APCP1`, `TCDC`, `RH`, `initstamp`.
55
+
56
+ Also returns `wgmodel` (resolution_real, initstamp) when needed for debugging.
57
+
58
+ ### SPA fallback (if simple `forecast` dies)
59
+
60
+ ```
61
+ GET …/windguru.net/int/iapi.php?q=forecast
62
+ &id_model=3&rundef=…&id_spot=201&WGCACHEABLE=21600&cache_index=…
63
+ ```
64
+
65
+ Same lesson as fli `_tfs`: params from `forecast_spot` tab rows.
66
+
67
+ ## WINDGURU DEFAULT weights
68
+
69
+ Port of SPA `di.calcSortByWeights` → `guru/search/blend.py`.
70
+
71
+ ```
72
+ res_w[id] = normalize( resolution ** (-2 * res_sensitivity) )
73
+ init_w[id] = normalize( ai(init_sensitivity, age_h) )
74
+ wgt_raw = res_w * init_w * model_koef[id]
75
+ weight = wgt_raw / Σ wgt_raw
76
+
77
+ ai(s, age) = max(0, 100*(age+4)^(-0.5*s) - 3*s*age)
78
+ age_h = (max_initstamp - initstamp) / 3600
79
+ normalize = scale so mean≈1, cap individual ≤1.5
80
+ ```
81
+
82
+ Skip `wave`, `virtual`, and id `100` (WG Mix table). Sort weight desc, then `priority` asc. Product: **top 3**.
83
+
84
+ ## Agent path
85
+
86
+ `spots` | `near` → `best` (preset locked) → ignore lower models.
87
+
88
+ ## Fixtures
89
+
90
+ | File | Source |
91
+ |------|--------|
92
+ | `search_castelldefels.json` | `search_spots` |
93
+ | `forecast_spot_201.json` | Castelldefels |
94
+ | `forecast_spot_48309.json` | De Slufter |
95
+ | `model_info_full.json` | live catalog |
96
+ | `forecast_201_gfs.json` | GFS sample |
97
+ | `spots_simplemap_nl.json` | NL bbox subset of simplemap |
98
+
99
+ Refresh: `python scripts/capture_fixtures.py`
100
+
101
+ ## Rate limits
102
+
103
+ Unknown. Retries via tenacity; prefer `WGCACHEABLE` on simplemap. Be polite.