plan_passage
Plan an A→B passage. Compare departure windows by default; pin a
single departure only when the user gives an explicit time.
## Tool routing — read this first
Before calling, classify the user's question:
1. **Pure weather lookup at a point** ("y aura-t-il du vent à Cassis
samedi à 14h ?", "quelles vagues dimanche au cap Sicié ?") — call
``get_marine_forecast`` and answer in text. Do NOT call
``plan_passage``: there's no route to plan.
2. **Trajet question with a flexible date** ("Marseille → Porquerolles
ce week-end", "demain ou après-demain", "dans les prochains jours")
— call ``plan_passage`` in **compare-windows mode**: pass
``latest_departure`` (e.g. earliest+48h) and ``sweep_interval_hours``
(3 or 6 typically) so the user sees several departure scenarios
side-by-side. Then pick 2-3 good ones and let the user choose.
This is the **default** for trajet planning — same API cost as a
single passage thanks to cache prewarm, much more value.
3. **Trajet with a precise hour pinned by the user** ("je pars demain
à 8h", "départ Saturday 9am") — call ``plan_passage`` in single
mode (no ``latest_departure``). Used for the final "show me the
detailed plan for THIS departure" view, often after step 2.
4. **Methodology question** ("comment c'est calculé ?",
"quelle efficacité par défaut ?") — call ``read_me``.
Rule of thumb: if the user does NOT give an exact hour, prefer
compare-windows. The widget renders one of the windows by default
and the chat lets the user pick another.
## Returned payload
Single mode:
- ``passage``: per-segment timing report (distance_nm, duration_h,
model used, segments[] with TWS/TWA/boat_speed/Hs, warnings).
- ``complexity``: 1-5 difficulty score with wind/sea breakdown and a
human-readable rationale.
- ``openwind_url``: deep-link to ohmywind.fr/plan that renders the
same passage in the standalone web app.
Compare-windows mode (``latest_departure`` set):
- ``mode``: ``"multi_window"``.
- ``sweep``: ``earliest`` / ``latest`` / ``interval_hours`` /
``window_count``.
- ``windows[]``: each entry has ``departure``, ``arrival``,
``duration_h``, ``distance_nm``, ``complexity`` (level + label +
rationale), ``conditions_summary`` (tws_min/max, predominant sail
angle, hs_min/max), ``warnings``, ``passage`` (full per-segment
report), ``complexity_full`` (full score), ``openwind_url``.
- ``meta_warnings``: top-level notes ("3 fenêtres ignorées …").
## How it renders
On hosts that support MCP Apps (Claude, Claude Desktop, ChatGPT, VS
Code Copilot, Goose, Postman, MCPJam), the response is automatically
accompanied by an interactive widget — the live ohmywind.fr/plan view
served via the ``ui://openwind/plan-passage`` resource declared on
this tool's ``_meta``. The widget reads ``openwind_url`` from the
structured output and embeds the matching plan view as an iframe.
On hosts without MCP Apps support (Cursor, Le Chat, terminal), present
a short text summary of the result (route, ETA, complexity, warnings)
and offer ``openwind_url`` as the "View full plan →" link.
## ALWAYS include the openwind_url(s) in your text reply
Even when the widget renders inline, the user wants the link spelled
out so they can open the full app, share it, or bookmark it. Treat
this as a hard requirement, not a fallback:
- **Single mode**: end your reply with a Markdown link built from the
``openwind_url`` field, e.g. ``[Voir le plan détaillé →](<openwind_url>)``.
Always use that value verbatim, never a URL you compose yourself: it
points at the environment this server is configured for, which is not
always the production site.
- **Compare-windows mode**: list 2-4 of the most relevant windows
and give each its own link, e.g.
``- Sam 2 mai 09h · 11h12 · ⚡2/5 — [voir →](url)``.
The user picks one from the chat, not the widget.
Phrase the link with intent ("voir le plan détaillé", "ouvrir cette
fenêtre dans l'app"), not just a bare URL — the user should know
what clicking does.
## Args
waypoints: list of ``{"lat": ..., "lon": ...}`` dicts (>=2). Caller
keeps the polyline off land — add intermediate waypoints to
skirt capes and peninsulas.
departure: ISO-8601 datetime, timezone-aware.
archetype: one of ``list_boat_archetypes()`` names.
efficiency: multiplier on polar speed. ``0.85`` racing, ``0.75``
cruising (default), ``0.65`` loaded family cruising, ``0.55``
heavy seas / fouled hull.
segment_length_nm: target sub-segment length. Default 10 nm
balances precision vs Open-Meteo budget; drop to 5 for tight
coastal work, raise to 20 for long offshore legs.
model: wind model. Default ``"auto"`` tries AROME (≤48 h) →
ICON-EU (≤5 d) → ECMWF IFS 0.25° (≤10 d) → GFS (≤16 d).
Pass an explicit name to bypass.
max_hs_m: optional max significant wave height (meters) over the
route — pass it if you have a sea-state estimate from
``get_marine_forecast`` and want it factored into the score.
Defaults to wind-only scoring.
motor_threshold_kn: optional sail-speed floor (knots) under which
the simulator switches to engine power. Must be paired with
``motor_speed_kn`` (either alone is ignored). Typical value
2 kn — sailors fire up the engine rather than crawl in light
wind. Leave unset for 100% sail. Range (0, 10].
motor_speed_kn: optional speed under engine (knots) applied to
segments where the sail estimate falls under
``motor_threshold_kn``. Typical 5-6 kn for a cruising boat.
Range (0, 12].
## Compare-windows mode (latest_departure set)
When ``latest_departure`` is provided, the tool switches into a
window-comparison call: it walks departure times from ``departure``
up to ``latest_departure`` every ``sweep_interval_hours`` (default
1 h). Returns ``{"mode": "multi_window", "sweep": {...}, "windows":
[...]}`` instead of the single-passage payload. Each window contains
``departure``, ``arrival``, ``duration_h``, ``distance_nm``,
``complexity``, ``conditions_summary``, ``warnings``, and its own
``openwind_url``.
``target_eta``: optional ISO-8601 datetime. When set, only windows that
arrive within ±2 h of the target are returned. If none match, all
windows are returned with a ``meta_warnings`` note.
## Failure modes
Raises ``ForecastHorizonError`` if the chosen model's horizon doesn't
cover the passage and ``model != "auto"``. The error message names the
failing model and suggests longer-range alternatives.