@meri-imperiumi/signalk-passage-briefing 0.3.0 → 0.5.0
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.
- package/CHANGELOG.md +112 -0
- package/README.md +39 -66
- package/SPEC.md +117 -0
- package/package.json +8 -1
- package/plugin/bulletin-engine.js +310 -15
- package/plugin/celestial-ephemeris.js +561 -0
- package/plugin/celestial-source.js +98 -20
- package/plugin/energy-source.js +167 -0
- package/plugin/fetch-engine.js +3 -0
- package/plugin/hazard-source.js +410 -0
- package/plugin/index.js +412 -7
- package/plugin/maritime-zones-source.js +366 -0
- package/plugin/notes-publisher.js +88 -1
- package/plugin/satellite-source.js +295 -0
- package/plugin/spool-watcher.js +87 -0
- package/plugin/weather-source.js +311 -0
- package/public/components/conditions-here.js +111 -2
- package/public/components/departure-control.js +170 -0
- package/public/components/horizon-sparkline.js +28 -7
- package/public/components/models.mjs +739 -59
- package/public/components/passage-outlook.js +300 -21
- package/public/components/passage-timeline.js +141 -0
- package/public/components/sk-api.js +108 -1
- package/public/components/strategic-outlook.js +87 -117
- package/public/components/tactical-dashboard.js +98 -59
- package/public/lines-of-interest.js +227 -0
- package/public/route-sim.mjs +525 -20
- package/public/sereno-physics.mjs +189 -5
- package/tests/bulletin-engine.test.js +493 -0
- package/tests/celestial-ephemeris.test.js +215 -0
- package/tests/celestial-source.test.js +57 -7
- package/tests/fetch-engine.test.js +1 -0
- package/tests/fixtures/bulletins/fqau23-ammc-western.txt +91 -0
- package/tests/fixtures/bulletins/fqps01-nffn.txt +33 -0
- package/tests/fixtures/bulletins/fqps43-nzkl-subtropic.txt +35 -0
- package/tests/fixtures/bulletins/fznt01-kwbc-hsf-at1.txt +139 -0
- package/tests/fixtures/bulletins/fzpn01-kwbc-hsf-ep1.txt +178 -0
- package/tests/fixtures/bulletins/fzpn03-knhc-hsf-ep2.txt +85 -0
- package/tests/fixtures/bulletins/fzpn40-phfo-hsf-np.txt +74 -0
- package/tests/fixtures/bulletins/fzps40-phfo-hsf-sp.txt +77 -0
- package/tests/fixtures/bulletins/hsfat2-knhc-2026-10-03.txt +55 -0
- package/tests/fixtures/bulletins/wtpz23-knhc-tcmep3.txt +157 -0
- package/tests/fixtures/energy-forecast-hourly.json +156 -0
- package/tests/fixtures/gdacs-rss-sample.xml +391 -0
- package/tests/hazard-source.test.js +239 -0
- package/tests/lines-of-interest.test.js +160 -0
- package/tests/maritime-zones-source.test.js +249 -0
- package/tests/notes-publisher.test.js +16 -0
- package/tests/openmeteo-mock.js +1 -0
- package/tests/plugin.test.js +480 -2
- package/tests/route-sim.test.js +419 -5
- package/tests/satellite-source.test.js +149 -0
- package/tests/sereno-physics.test.js +165 -0
- package/tests/weather-source.test.js +272 -0
- package/tests/webapp.test.js +731 -90
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,118 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.5.0] - 2026-10-03
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Daylight-anchored departure time (work doc #15): while the boat is moored, the whole forecast schedule anchors to a realistic departure instead of "now" — nobody casts off at 02:40 because the forecast said so. Auto mode: underway sails from now; night at the start position waits for next first light (civil dawn, −6° sun altitude at the route's first waypoint); day adds the prep delay (1.5 h, configurable 0.5–3) unless the sun would set within it, in which case the anchor is the next dawn — never an immediate departure right before dark (the recorded decision). The pure helper (`assumedDepartureTime` in sereno-physics) detects a rising sun crossing with 5-minute steps bounded at 18 h and states what it assumed (`reason`), including a polar `no_dawn` fallback. The tactical and strategic views share a departure control (Auto / Now / First light / +1 h / +2 h / custom) whose manual choice overrides auto until it is put back; the root owns the state, re-checks the anchor every 10 minutes, and re-simulates only when the anchor moved more than 10 minutes so the view doesn't flap around dawn. The tactical chip states the anchor ("First light 10-05 06:00 +13"), the strategic ETA header says "Assumed departure first light …" ("Underway — from now" when sailing), sparkline titles read wall-clock stamps while shifted, and a delayed departure appears in the timeline as a ⚓ event ("Departure at first light" / "Departure after prep"). The served payload carries the same `departure` metadata so external consumers see numbers consistent with the auto assumption. SPEC §2.1/§3.1 updated.
|
|
9
|
+
- Energy forecast consumption (work doc #10): the briefing finally powers its energy views — in the predictor's own terms. signalk-energy-predictor's hourly forecast (`electrical.energy.prediction.forecast.hourly`, the whole 48 h series as one delta value) and its outlook paths (`status`, `net`, `surplus` + window, `timeToEmpty`) are subscribed like any other Signal K path; the hourly series adapts into the frozen `energyHourly` consumer contract at compile time (the contract's `solarWh` carries total ideal generation: solar + wind + hydro + alternator) and rides the briefing payload so the offline hours re-derive from the cache. The 24 h solar-yield strip and the deficit banner work for the first time; the conditions-here view gains an "Energy 24h" line (the passage strip's here-mode sibling at SOG 0). New in the timeline: the predictor's own energy events — **surplus** is forecast curtailment (battery full while yield continues; the event carries the curtailed Wh and its window, "run opportunistic loads"), **deficit** and **critical** come from the outlook status (the event carries the 24 h net and, when the trajectory names one, the depletion time) — no independent re-derivation with invented thresholds.
|
|
10
|
+
- Territorial waters transitions along the route (work doc #17): the briefing now says where the route crosses the boundaries that change the practical picture on board — entering or leaving a country's internal, archipelagic or 12 NM waters (the metered-ocean data boundary, customs and discharge rules). Built on `@openwaters/maritime-zones` (MIT) over the Marine Regions Maritime Boundaries Geodatabase (VLIZ, CC-BY 4.0 — the "not for navigation, no legal value" disclaimer rides the payload and renders wherever zones do). Corridor tiles (about 8 MB for a 900 nm crossing) download during the online window into the plugin data directory and answer offline afterwards; the boundary walk resamples the route at 1 nm so short territorial hops are not missed. The route simulation timestamps each crossing against the passage schedule, the unified timeline renders enter/leave events (leave events carry the "ocean data rules beyond this point" connectivity note), and here mode reports the waters the vessel sits in right now in the conditions view. Missing tiles, older Node (the package needs ≥ 24, the plugin allows 22.5) or a failed download degrade to no zone data — never a failed briefing. — entering or leaving a country's internal, archipelagic or 12 NM waters (the metered-ocean data boundary, customs and discharge rules). Built on `@openwaters/maritime-zones` (MIT) over the Marine Regions Maritime Boundaries Geodatabase (VLIZ, CC-BY 4.0 — the "not for navigation, no legal value" disclaimer rides the payload and renders wherever zones do). Corridor tiles (about 8 MB for a 900 nm crossing) download during the online window into the plugin data directory and answer offline afterwards; the boundary walk resamples the route at 1 nm so short territorial hops are not missed. The route simulation timestamps each crossing against the passage schedule, the unified timeline renders enter/leave events (leave events carry the "ocean data rules beyond this point" connectivity note), and here mode reports the waters the vessel sits in right now in the conditions view. Missing tiles, older Node (the package needs ≥ 24, the plugin allows 22.5) or a failed download degrade to no zone data — never a failed briefing.
|
|
11
|
+
- GDACS hazard events also surface in the conditions-here view: earthquakes, cyclones and floods near the vessel are critical while stationary, so the here view lists them (Red events highlighted) alongside the passage timeline.
|
|
12
|
+
- GDACS hazard event feed (work doc #22): earthquakes, tropical cyclones, floods and volcanic activity from the EU JRC's `gdacs.org/xml/rss.xml`, fetched during the online window and cached in `weather/hazards.json` with id-based dedup (repolls update in place). Events filter route-relative — alert level at or above the configured minimum (default Orange), within 500 nm of the route corridor or 1000 nm of the vessel, and no older than 72 h — and ride the briefing payload as `hazardEvents`, render in the unified timeline as `hazard` events with the alert level setting the severity, and publish as georeferenced `resources/notes` chart notes (category `hazard-event`, provenance `GDACS`) with scoped expiry that leaves the METAREA notes and other clients' notes untouched. Zero XML dependencies: a targeted extractor reads the stable RSS shape; malformed items skip without costing the feed.
|
|
13
|
+
- Slatting & roll-dampening penalty in the Sereno comfort index (work doc #14): a residual swell (combined Hs ≥ 0.6 m) with too little wind to keep the sails loaded — below 7 kn upwind, below 12 kn downwind — now scores as the wash machine it is: the vertical acceleration carries a 2.5× discomfort multiplier before banding and the hour is forced to at least Rough. The hourly rows tag `slatting: true`, and the tactical dashboard's sparkline renders those Rough blocks with a diagonal hatch (alternating `--comfort-rough` over the card background) so the skipper sees the discomfort is light-air slatting, not heavy weather. SPEC §5.2 amended.
|
|
14
|
+
- Real-world bulletin fixtures (work doc #4 follow-up): ten received-as-is High Seas texts — NHC Atlantic T1/T2, OPC East Pacific EP1, NHC EP2, Honolulu North and South Pacific (NP/SP), Fiji NAVAREA XIV, MetService NZ Subtropic, Australian BoM Western X and an NHC hurricane advisory — live under `tests/fixtures/bulletins/` with a per-family regression suite asserting issue metadata, issuer and geography extraction.
|
|
15
|
+
- The bulletin engine now parses the NHC/OPC/PHFO High Seas families: compact coordinate pairs (`13N67W`, no separator) normalize into the spaced form; a hemisphere-less second number that is not the dateline shorthand is rejected as prose ("07N TO 31N" is two latitudes, not 7N 31E); issue lines from NHC (`0430 UTC SAT OCT 3 2026`), PHFO (`1130 UTC TUE FEB 17 2026`), Fiji (`OCT 022000 UTC`, mixed case `Oct` from the WMO bulletin sets), MetService New Zealand (`Wellington issued at 021856UTC`) and the Australian BoM (`commencing 2300 UTC 2 October 2026`) all parse to real timestamps; `.FORECASTER <NAME>.` lines identify the issuer when no `ISSUED BY` exists, and a leading article is trimmed ("Issued by the Australian Government Bureau of Meteorology").
|
|
16
|
+
- NWS's renamed Gulf is normalized back to the crew's name: "GULF OF AMERICA" reads "GULF OF MEXICO" everywhere the cleaned bulletin text is shown or matched.
|
|
17
|
+
- Lines of interest (work doc #1): the briefing detects where the planned passage crosses the traditional ceremonial lines — the equator (Shellback ceremony), the tropics, the polar circles (Blue Nose / Red Nose), the prime meridian and the antimeridian (Domain of the Golden Dragon, with the calendar skip/repeat note) — reading the simulated track, so each crossing's position, distance from start and ETA come from the boat's own schedule rather than the forecast window. The crossings ride the unified passage timeline as `line` events (⌀ glyph, the general form the timeline was built for) instead of a separate strategic block; `passageSummary.linesOfInterest` carries the data, `lines_of_interest_enabled` (default on) turns the feature off.
|
|
18
|
+
- Celestial ephemeris engine (work doc #3 Phase 2): the sky is computed, not fetched. A new `plugin/celestial-ephemeris.js` module built on `astronomy-engine` (MIT, zero transitive dependencies — the dependency decision is made) derives planetary conjunctions and oppositions during nautical night, and meteor shower peaks (static annual calendar) gated to dark, clear, visible nights: moon set or a crescent, radiant able to clear 20° from the vessel's latitude, forecast cloud cover under 30 %. Bright station passes (ISS, Tiangong) are propagated locally from CelesTrak two-line elements with `satellite.js` (MIT — the SGP4 propagator call resolved) in `plugin/satellite-source.js`, reported when the pass peaks over 15° in nautical night under a clear enough sky. Every event passes the full tactical visibility gate the work document specifies — nautical night, altitude over 15° to clear the marine boundary-layer haze, cloud cover, moonlight — and the Phase-1 aurora alert is upgraded onto the same gate (nautical night replaces the sunset-based check; moonlit or overcast nights no longer raise aurora banners). Briefing payloads gain `celestialNights` — per-night twilight times (civil, nautical, astronomical) and moon phase/illumination — and `TimeStepForecast` gains `surface.cloudCover` (Open-Meteo `cloud_cover`; the Weather API provider publishes none and maps null, which leaves the cloud gate open).
|
|
19
|
+
- The timeline's night stamps show the actual moon phase instead of a fixed crescent: `mergeTimeline` picks one of the eight moon glyphs (🌑🌒🌓🌔🌕🌖🌗🌘) for each night-flagged event from the payload's `celestialNights`, falling back to ☾ for payloads that predate the field. The strategic ETA table's night-arrival markers (P10/P50/P90) get the same treatment.
|
|
20
|
+
- **METAREA notes now carry provenance: who published, when.** Every note the bulletin pipeline serves in `resources/notes` is stamped in its `properties` with `publishedBy` — the issuing authority parsed from the bulletin header ("ISSUED BY FIJI METEOROLOGICAL SERVICE …" → `FIJI METEOROLOGICAL SERVICE`), falling back to `signalk-passage-briefing` when the bulletin names no service (structured UKHO warnings, some NWS products) — and `publishedAt`, the bulletin's issue time. Chart plotters can show the line "Published on 10-03 12:00Z by FIJI METEOROLOGICAL SERVICE" straight from the resource; the DR webapp's note detail surface does.
|
|
21
|
+
|
|
22
|
+
## [0.4.0] - 2026-10-03
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- Sail changes are scheduled when the crew can act on them:
|
|
27
|
+
recommendation-driven canvas changes anchor to the *previous watch
|
|
28
|
+
handover* when a watch schedule is running (signalk-watch-schedule;
|
|
29
|
+
boundaries are extrapolated across the forecast horizon from the
|
|
30
|
+
rotation cycle, and the watch boundary wins over sunlight),
|
|
31
|
+
otherwise to the next sunrise/sunset. Several detections between
|
|
32
|
+
boundaries collapse into one change carrying the last suggested
|
|
33
|
+
state, no-op re-rigs are dropped, and the timeline says which
|
|
34
|
+
anchor applied ("watch change" / "at dusk" / "at dawn"). Tacks and
|
|
35
|
+
gybes stay at their tactical times.
|
|
36
|
+
- Unified passage timeline: a new `<passage-timeline>` component and
|
|
37
|
+
`mergeTimeline()` view model render every event source — sail
|
|
38
|
+
changes, planned tacks and gybes, convective risk, macro sea state,
|
|
39
|
+
territorial waters transitions, sky events and hazard notes — as
|
|
40
|
+
one chronological list with a time gutter, kind glyph and severity
|
|
41
|
+
colour. The tactical dashboard renders the 24 h slice, the
|
|
42
|
+
strategic outlook the whole passage; the per-type blocks (Sail
|
|
43
|
+
Work, Convective Risk, Macro Sea State, Sky Notes) and the tactical
|
|
44
|
+
hazard banners are replaced by it. The exception view now carries
|
|
45
|
+
the whole-route hazard list in `passageSummary.hazards` (the 24 h
|
|
46
|
+
`next24h.sailChanges` and `next24h.hazards` slices are gone — the
|
|
47
|
+
timeline slices itself).
|
|
48
|
+
- Sail-change events in the timeline carry the forecast conditions
|
|
49
|
+
at the change point (nearest simulated hour): true wind, sea state
|
|
50
|
+
and Sereno comfort tier — "Main 1 reef — 14.2 kn TWS · Hs 1.5 m ·
|
|
51
|
+
coffee" — so the crew knows what they are rigging into. The
|
|
52
|
+
simulation's hourly rows now include wave height and period.
|
|
53
|
+
- Ship's time display: the webapp reads the vessel's published
|
|
54
|
+
timezone (`environment.time.timezoneOffset` / `.timezoneRegion`, as
|
|
55
|
+
served by signalk-ships-time) over the REST API and the delta
|
|
56
|
+
stream, and renders briefing stamps in ship's time (`MM-DD HH:MM
|
|
57
|
+
+13`) instead of UTC, falling back to UTC `Z` when no offset is
|
|
58
|
+
published. The offset rides on every stamp so a zone crossing
|
|
59
|
+
mid-passage reads honestly; a header pill names the zone (IANA
|
|
60
|
+
region when known, else the offset).
|
|
61
|
+
- Weather source selection (`weather_source`: `auto` / `weather-api` /
|
|
62
|
+
`open-meteo`, default `auto`): when the server has the Weather API
|
|
63
|
+
and a provider answers — signalk-weather-router-plus serving its
|
|
64
|
+
decoded ECMWF run — waypoint forecasts for the here and route
|
|
65
|
+
briefing windows are read from it in-process (`app.weatherApi`)
|
|
66
|
+
instead of Open-Meteo, so the briefing reasons from the same
|
|
67
|
+
forecast the router planned with and offshore fetches stay local.
|
|
68
|
+
Provider responses (Signal K units) map onto the payload
|
|
69
|
+
conventions (knots, degrees true, hPa); combined sea only — swell
|
|
70
|
+
and wind-sea partitions and the upper-air fields behind the
|
|
71
|
+
convective warnings degrade to absent. A failed Weather API fetch
|
|
72
|
+
falls back to Open-Meteo for that window; the forced choices never
|
|
73
|
+
fall back.
|
|
74
|
+
|
|
75
|
+
### Changed
|
|
76
|
+
|
|
77
|
+
- Convective warnings read as episodes, not hourly spam: consecutive
|
|
78
|
+
anomalies (and steep-sea anomalies alike) merge into one timeline
|
|
79
|
+
event with a time range and peak values — "CAPE 713 J/kg · K 28.6 ·
|
|
80
|
+
until 10-05 05:38Z". Severity follows the peak: CAPE ≥ 400 J/kg (the
|
|
81
|
+
bar where weather services start coloring the index) or K-index ≥ 30
|
|
82
|
+
is a red alert, while the unstable-air band below it (K ≥ 28 with
|
|
83
|
+
low CAPE) still shows as an orange warning with units and sane
|
|
84
|
+
precision instead of being dropped or inflated.
|
|
85
|
+
|
|
86
|
+
### Fixed
|
|
87
|
+
|
|
88
|
+
- The webapp flags stale cached briefings: when a served payload was
|
|
89
|
+
compiled more than a day ago, a banner above the briefing shows the
|
|
90
|
+
compile stamp and age with a Fetch now affordance, instead of
|
|
91
|
+
presenting a multi-day-old timeline's `+Xh` labels as upcoming.
|
|
92
|
+
- The plugin re-fetches stale briefings without waiting for an edge
|
|
93
|
+
trigger: the oneshot fires only when the internet state changes and
|
|
94
|
+
cron windows only run while moored and charged, so a server that
|
|
95
|
+
stays up for days while the machine sits in STANDBY_OFFSHORE kept
|
|
96
|
+
serving a briefing sliding into the past. The one-minute ticker now
|
|
97
|
+
re-fetches (trigger `stale`) when online and the cached briefing
|
|
98
|
+
(active route, else last briefed, else here) is older than the
|
|
99
|
+
route TTL — at most once per six hours, and only when something is
|
|
100
|
+
actually cached.
|
|
101
|
+
|
|
102
|
+
- Bulletin geography now resolves named synoptic features in area
|
|
103
|
+
bounds: `SOUTH OF 09S AND WEST OF CF` and `SOUTH OF 10S, BETWEEN
|
|
104
|
+
150W AND CF` compose a polygon from the cold front's defining
|
|
105
|
+
chain (clipped to the stated latitude bounds, closed across the
|
|
106
|
+
antimeridian with a margin so western-Pacific vessels stay in
|
|
107
|
+
west-of-front areas) instead of falling back to a hemisphere-wide
|
|
108
|
+
box that matched every vessel south of the bound.
|
|
109
|
+
|
|
110
|
+
- Coordinate chains accept the Fiji/NFFN bulletin conventions the
|
|
111
|
+
strict parser dropped: the dateline written as bare `180` (`TROUGH
|
|
112
|
+
T3 12S 175E 14S 180 15S 177W`) and the equator written as `EQT`
|
|
113
|
+
(`EQT 177E`). Prose numbers (`280600 UTC`, `20 TO 30 KNOTS`) are
|
|
114
|
+
still rejected, and a rejected span no longer swallows a following
|
|
115
|
+
coordinate pair.
|
|
116
|
+
|
|
5
117
|
## [0.3.0] - 2026-09-28
|
|
6
118
|
|
|
7
119
|
### Added
|
package/README.md
CHANGED
|
@@ -1,85 +1,58 @@
|
|
|
1
1
|
# signalk-passage-briefing
|
|
2
2
|
|
|
3
|
-
Offshore passage daily briefing webapp for Signal K: a plugin that plans
|
|
4
|
-
and reviews passages for a cruising sailing vessel.
|
|
3
|
+
Offshore passage daily briefing webapp for Signal K: a plugin that plans and reviews passages for a cruising sailing vessel.
|
|
5
4
|
|
|
6
|
-
The plugin fetches
|
|
7
|
-
or via a GRIB/text spool when offline offshore), runs a step-forward
|
|
8
|
-
isochrone simulation with a monohull comfort model, learns the crew's
|
|
9
|
-
sail preferences from the electronic logbook, and serves a two-screen
|
|
10
|
-
webapp: a 24-hour tactical dashboard and a strategic passage summary.
|
|
11
|
-
See [SPEC.md](SPEC.md) for the full design.
|
|
5
|
+
The plugin fetches weather along the planned route — from the server's Weather API when a provider answers, Open-Meteo otherwise; fetched online, or via a GRIB/text spool when offline offshore. It runs a step-forward isochrone simulation with a monohull comfort model, learns the crew's sail preferences from the electronic logbook, and serves a two-screen webapp: a 24-hour tactical dashboard and a strategic passage summary. See [SPEC.md](SPEC.md) for the full design.
|
|
12
6
|
|
|
13
|
-
Part of the Lille Ø offshore suite, alongside
|
|
14
|
-
[@meri-imperiumi/signalk-energy-predictor](https://github.com/meri-imperiumi/signalk-energy-predictator)
|
|
15
|
-
and [@meri-imperiumi/signalk-logbook](https://github.com/meri-imperiumi/signalk-logbook).
|
|
7
|
+
Part of the Lille Ø offshore suite, alongside [@meri-imperiumi/signalk-energy-predictor](https://github.com/meri-imperiumi/signalk-energy-predictator) and [@meri-imperiumi/signalk-logbook](https://github.com/meri-imperiumi/signalk-logbook).
|
|
16
8
|
|
|
17
9
|
## Data sources
|
|
18
10
|
|
|
19
11
|
### Signal K
|
|
20
12
|
|
|
21
|
-
- `navigation.course.activeRoute` — the route being sailed; wins over
|
|
22
|
-
|
|
23
|
-
- `
|
|
24
|
-
|
|
25
|
-
- `
|
|
26
|
-
and backfill only fetch while `online` or `metered`)
|
|
27
|
-
- `navigation.state`, `electrical.batteries.house.capacity.stateOfCharge`
|
|
28
|
-
— state machine inputs (moored/anchored/sailing, publication windows)
|
|
29
|
-
- `polars.activePolar`, `polars.performanceFactor` — the canonical
|
|
30
|
-
polar resource consumed for boat speed (falls back to a bundled
|
|
31
|
-
default table)
|
|
13
|
+
- `navigation.course.activeRoute` — the route being sailed; wins over the last briefed route when the cron/oneshot fetch window opens
|
|
14
|
+
- `navigation.position` — vessel position for the conditions-here empty state and position-only bulletin filtering
|
|
15
|
+
- `network.internet.state` — connectivity gating (weather, bulletins and backfill only fetch while `online` or `metered`)
|
|
16
|
+
- `navigation.state`, `electrical.batteries.house.capacity.stateOfCharge` — state machine inputs (moored/anchored/sailing, publication windows)
|
|
17
|
+
- `polars.activePolar`, `polars.performanceFactor` — the canonical polar resource consumed for boat speed (falls back to a bundled default table)
|
|
32
18
|
- Resources API — route geometries and polar tables
|
|
33
|
-
- History API (on board) — wind/attempt snapshots for the sail-event
|
|
34
|
-
|
|
35
|
-
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
- [signalk
|
|
39
|
-
|
|
40
|
-
preference matrix
|
|
41
|
-
- [@signalk/sailsconfiguration](https://www.npmjs.com/package/@signalk/sailsconfiguration)
|
|
42
|
-
— sail inventory used to filter free-text noise out of log entries
|
|
19
|
+
- History API (on board) — wind/attempt snapshots for the sail-event backfill
|
|
20
|
+
- Published tile paths — `navigation.briefing.generatedAt` / `.route` / `.hasNew` / `.comfort` drive the plotter-extension tile (`.acknowledgedAt` is writable to clear the NEW badge)
|
|
21
|
+
- [signalk-logbook](https://github.com/meri-imperiumi/signalk-logbook) store — crewed sail events (reefs, sail changes) that train the preference matrix
|
|
22
|
+
- [signalk-ships-time](https://github.com/meri-imperiumi/signalk-ships-time) — `environment.time.timezoneOffset` / `.timezoneRegion`, the vessel's published timezone: briefing stamps render in ship's time when available (the offset rides on every stamp), falling back to UTC `Z` when nothing is published
|
|
23
|
+
- [signalk-watch-schedule](https://github.com/hoeken/signalk-watch-schedule) — `watch.state.onWatch`, `watch.state.startedAt`, `watch.system` and `watch.schedule`: the running watch rotation. While a watch is running, planned sail changes anchor to the previous watch handover (both teams awake) instead of sunrise/sunset; boundaries are extrapolated across the forecast horizon from the rotation cycle
|
|
24
|
+
- [@signalk/sailsconfiguration](https://www.npmjs.com/package/@signalk/sailsconfiguration) — sail inventory used to filter free-text noise out of log entries
|
|
25
|
+
- Weather API (`app.weatherApi`) — waypoint forecasts from the registered provider, the preferred source (`weather_source: auto`) whenever one answers; [signalk-weather-router-plus](https://github.com/motamman/signalk-weather-router-plus) serves it from its decoded ECMWF run, so briefing numbers match what the router planned with. That source carries combined sea only: swell/wind-sea partitions and the upper-air fields behind the convective warnings degrade to absent rather than being invented
|
|
43
26
|
|
|
44
27
|
### External
|
|
45
28
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- [NOAA SWPC planetary K-index forecast](https://services.swpc.noaa.gov/products/noaa-planetary-k-index-forecast.json)
|
|
50
|
-
|
|
51
|
-
- [
|
|
52
|
-
|
|
53
|
-
- [
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
All external fetches are online-gated and cached to the plugin data
|
|
71
|
-
directory, so the last payloads survive the offline hours.
|
|
29
|
+
The Open-Meteo entries below are the fallback weather source: used when no Weather API provider answers (or `weather_source` is `open-meteo`), and the only source of the partition and upper-air fields the Weather API providers don't publish.
|
|
30
|
+
|
|
31
|
+
- [Open-Meteo Forecast API](https://open-meteo.com/en/docs) — surface wind, gusts, MSL pressure, CAPE and the pressure-layer fields behind the K-index (best-match model)
|
|
32
|
+
- [NOAA SWPC planetary K-index forecast](https://services.swpc.noaa.gov/products/noaa-planetary-k-index-forecast.json) — geomagnetic activity behind the aurora advisories
|
|
33
|
+
- [JPL SBDB query API](https://ssd-api.jpl.nasa.gov/doc/sbdb_query.html) — comet brightness parameters (M1/K1) behind the Sky Notes comets
|
|
34
|
+
- [CelesTrak](https://celestrak.org/NORAD/elements/gp.php?GROUP=stations&FORMAT=tle) — two-line elements for the crewed stations (ISS, Tiangong); bright satellite passes are propagated locally from the fetched TLEs with SGP4, gated to nautical night, clear sky and a 15° haze line
|
|
35
|
+
- [NOAA TGFTP radiofax tree](https://tgftp.nws.noaa.gov/fax/) and the [BoM difacs charts](http://www.bom.gov.au/difacs/) — synoptic surface-analysis charts per METAREA zone (work doc #11), per the schedule in [otherfax.txt](https://tgftp.nws.noaa.gov/fax/otherfax.txt)
|
|
36
|
+
- [UKHO Admiralty MSI API](https://msi.admiralty.co.uk/) — structured NAVAREA navigational warnings for the resolved zone; the third rung of the GMDSS bulletin fetch ladder (TGFTP raw text, WMO GMDSS portal, then UKHO MSI JSON) and also accepted verbatim as a `bulletin_urls` source
|
|
37
|
+
- [GDACS RSS](https://www.gdacs.org/xml/rss.xml) — EU JRC global disaster alerts (earthquakes, tropical cyclones, floods, volcanic activity), filtered route-relative by alert level and corridor distance: they ride the unified passage timeline and publish as georeferenced chart notes (work doc #22)
|
|
38
|
+
- [Open-Meteo Marine API](https://open-meteo.com/en/docs) — NOAA GFS-Wave 0.25° combined sea/wind sea/swell partitions, and Météo-France SMOC surface currents
|
|
39
|
+
- NOAA TGFTP (`tgftp.nws.noaa.gov`) — raw METAREA bulletin text for the resolved GMDSS zone's station (e.g. `FQPS01 NFFN` for XIV)
|
|
40
|
+
- WMO GMDSS portal (`weather.gmdss.org`) — per-zone bulletin pages, fallback when the TGFTP station is not configured
|
|
41
|
+
- api.weather.gov product API — US High Seas Forecast texts (default `bulletin_urls`: HSF NP/EP1/EP2); extra sources can be added via the `bulletin_urls` configuration (plain text or api.weather.gov product URLs; UKHO MSI JSON works too)
|
|
42
|
+
|
|
43
|
+
All external fetches are online-gated and cached to the plugin data directory, so the last payloads survive the offline hours.
|
|
44
|
+
|
|
45
|
+
## Pairing with Weather Router Plus
|
|
46
|
+
|
|
47
|
+
[signalk-weather-router-plus](https://github.com/motamman/signalk-weather-router-plus) plans the passage: isochrone routing against the vessel's polar on the ECMWF open-data run it keeps decoded on disk, with map overlays, tides and currents. Activate the route it publishes to the Resources API, and this plugin briefs it — `navigation.course.activeRoute` wins over the last briefed route at the next fetch window, and with `weather_source: auto` (the default) the briefing reads its forecasts from the router's Weather API provider. One forecast on board, planner and briefing in agreement: the router for planning and visualization, the briefing for the underway daily routine (comfort, sail changes, bulletins, energy).
|
|
48
|
+
|
|
49
|
+
## Pairing with Watch Schedule
|
|
50
|
+
|
|
51
|
+
[signalk-watch-schedule](https://github.com/hoeken/signalk-watch-schedule) runs the crew's watch rotation and publishes it under `watch.*`. When a watch is running, this plugin reads the schedule at app load and moves planned sail changes (reefs, canvas work) to the *previous* watch handover — the moment both teams are awake on deck — rather than sunrise/sunset; tacks and gybes stay at their tactical times, since course work can't wait for a handover. The watch boundary wins over sunlight. Without a running watch, canvas work anchors to the next sunrise/sunset. Webapp-side only: no configuration, and the briefing works unchanged when the plugin is absent.
|
|
72
52
|
|
|
73
53
|
## Acknowledgments
|
|
74
54
|
|
|
75
|
-
The comfort model and the whole idea of "what will each departure
|
|
76
|
-
actually feel like" come from SV Sabado's
|
|
77
|
-
[passage-weather](https://github.com/sailing12388/passage-weather)
|
|
78
|
-
ensemble departure planner by Ray Hendricks. The Sereno comfort scale
|
|
79
|
-
(Champagne, Easy, Coffee, Rough, Sick), the encounter-period motion
|
|
80
|
-
math, and the ISO 2631-1 comfort bands are adapted from it for a
|
|
81
|
-
monohull (heel and waterline-pitch resonance instead of catamaran
|
|
82
|
-
beam-roll and bridgedeck slam).
|
|
55
|
+
The comfort model and the whole idea of "what will each departure actually feel like" come from SV Sabado's [passage-weather](https://github.com/sailing12388/passage-weather) ensemble departure planner by Ray Hendricks. The Sereno comfort scale (Champagne, Easy, Coffee, Rough, Sick), the encounter-period motion math, and the ISO 2631-1 comfort bands are adapted from it for a monohull (heel and waterline-pitch resonance instead of catamaran beam-roll and bridgedeck slam).
|
|
83
56
|
|
|
84
57
|
## License
|
|
85
58
|
|
package/SPEC.md
CHANGED
|
@@ -77,6 +77,52 @@ signalk-passage-outlook/
|
|
|
77
77
|
"type": "number",
|
|
78
78
|
"title": "Pitching Acceleration Multiplier Constant",
|
|
79
79
|
"default": 0.40
|
|
80
|
+
},
|
|
81
|
+
"lines_of_interest_enabled": {
|
|
82
|
+
"type": "boolean",
|
|
83
|
+
"title": "Lines of Interest (ceremonial crossings)",
|
|
84
|
+
"default": true
|
|
85
|
+
},
|
|
86
|
+
"hazard_events_enabled": {
|
|
87
|
+
"type": "boolean",
|
|
88
|
+
"title": "GDACS Hazard Events",
|
|
89
|
+
"default": true
|
|
90
|
+
},
|
|
91
|
+
"hazard_min_alert_level": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"enum": ["green", "orange", "red"],
|
|
94
|
+
"title": "Minimum GDACS Alert Level",
|
|
95
|
+
"default": "orange"
|
|
96
|
+
},
|
|
97
|
+
"hazard_radius_offroute_nm": {
|
|
98
|
+
"type": "number",
|
|
99
|
+
"title": "Hazard Off-route Radius (nm)",
|
|
100
|
+
"default": 500
|
|
101
|
+
},
|
|
102
|
+
"hazard_radius_ahead_nm": {
|
|
103
|
+
"type": "number",
|
|
104
|
+
"title": "Hazard Vessel Radius (nm)",
|
|
105
|
+
"default": 1000
|
|
106
|
+
},
|
|
107
|
+
"hazard_max_age_hours": {
|
|
108
|
+
"type": "number",
|
|
109
|
+
"title": "Hazard Event Aging (hours)",
|
|
110
|
+
"default": 72
|
|
111
|
+
},
|
|
112
|
+
"departure_daylight_auto": {
|
|
113
|
+
"type": "boolean",
|
|
114
|
+
"title": "Anchor Departure to Daylight (auto)",
|
|
115
|
+
"default": true
|
|
116
|
+
},
|
|
117
|
+
"departure_prep_hours": {
|
|
118
|
+
"type": "number",
|
|
119
|
+
"title": "Departure Prep Delay (hours)",
|
|
120
|
+
"default": 1.5
|
|
121
|
+
},
|
|
122
|
+
"departure_dawn_altitude_deg": {
|
|
123
|
+
"type": "number",
|
|
124
|
+
"title": "First Light Sun Altitude (degrees)",
|
|
125
|
+
"default": -6
|
|
80
126
|
}
|
|
81
127
|
}
|
|
82
128
|
}
|
|
@@ -144,6 +190,63 @@ interface UnifiedWeatherPayload {
|
|
|
144
190
|
issuedAt: string;
|
|
145
191
|
bulletinText: string;
|
|
146
192
|
};
|
|
193
|
+
spaceEvents?: {
|
|
194
|
+
kind: string; // 'aurora' | 'comet' | 'conjunction' | 'opposition' | 'meteor'
|
|
195
|
+
timestamp: string; // ISO timestamp
|
|
196
|
+
tactical: boolean; // 24h dashboard banner vs strategic sky note
|
|
197
|
+
description: string;
|
|
198
|
+
}[];
|
|
199
|
+
celestialNights?: {
|
|
200
|
+
timestamp: string; // ISO timestamp (night anchor)
|
|
201
|
+
moonPhaseDeg: number; // 0 = new, 90 = first quarter, 180 = full, 270 = third quarter
|
|
202
|
+
moonIllumination: number; // fraction 0-1
|
|
203
|
+
civilDusk: string | null; // ISO timestamps, null beyond polar day/night
|
|
204
|
+
nauticalDusk: string | null;
|
|
205
|
+
astronomicalDusk: string | null;
|
|
206
|
+
astronomicalDawn: string | null;
|
|
207
|
+
nauticalDawn: string | null;
|
|
208
|
+
civilDawn: string | null;
|
|
209
|
+
}[];
|
|
210
|
+
hazardEvents?: {
|
|
211
|
+
id: string; // GDACS event id
|
|
212
|
+
type: string; // 'EQ' | 'TC' | 'FL' | 'VO' | 'DR' | 'WF'
|
|
213
|
+
alertLevel: string; // 'green' | 'orange' | 'red'
|
|
214
|
+
title: string;
|
|
215
|
+
description: string;
|
|
216
|
+
timestamp: string; // ISO, newer of pubDate/datemodified
|
|
217
|
+
lat: number;
|
|
218
|
+
lon: number;
|
|
219
|
+
distanceNm: number; // from the admitting reference (vessel or route)
|
|
220
|
+
bearingDeg: number; // degrees true from the same reference
|
|
221
|
+
link: string | null; // GDACS event page
|
|
222
|
+
}[];
|
|
223
|
+
zoneTransitions?: {
|
|
224
|
+
kind: 'enter' | 'leave'; // boundary crossing direction
|
|
225
|
+
territory: { name: string; iso_ter: string };
|
|
226
|
+
lat: number; // crossing position
|
|
227
|
+
lon: number;
|
|
228
|
+
distanceFromStartNm: number;
|
|
229
|
+
connectivity?: 'ocean'; // leave events: metered-ocean rules beyond
|
|
230
|
+
}[];
|
|
231
|
+
zonesHere?: { // here mode: waters the vessel sits in
|
|
232
|
+
layer: string; // 'internal' | 'archipelagic' | '12nm'
|
|
233
|
+
name: string;
|
|
234
|
+
iso_ter: string;
|
|
235
|
+
territory: string;
|
|
236
|
+
}[];
|
|
237
|
+
zoneDisclaimer?: string; // Marine Regions attribution + no-navigation notice
|
|
238
|
+
energyHourly?: {
|
|
239
|
+
timestamp: string; // ISO hour stamp
|
|
240
|
+
solarWh: number; // total ideal generation (solar+wind+hydro+alternator)
|
|
241
|
+
loadWh: number; // house consumption
|
|
242
|
+
netWh: number; // solarWh - loadWh
|
|
243
|
+
soc: number | null; // ideal SoC 0-1 at hour end
|
|
244
|
+
}[]; // signalk-energy-predictor forecast, 48 h
|
|
245
|
+
departure?: {
|
|
246
|
+
assumed: boolean; // auto daylight anchor vs crew override
|
|
247
|
+
time: string; // ISO — the schedule's anchor instant
|
|
248
|
+
reason: string; // 'underway' | 'next_dawn' | 'daylight_prep' | 'no_dawn' | 'manual'
|
|
249
|
+
};
|
|
147
250
|
}
|
|
148
251
|
|
|
149
252
|
interface TimeStepForecast {
|
|
@@ -152,6 +255,7 @@ interface TimeStepForecast {
|
|
|
152
255
|
tws: number; // knots
|
|
153
256
|
twd: number; // degrees true
|
|
154
257
|
mslp: number; // hPa
|
|
258
|
+
cloudCover: number | null; // percent (0-100); null when the source carries none
|
|
155
259
|
};
|
|
156
260
|
marine: {
|
|
157
261
|
hsCombined: number; // meters
|
|
@@ -351,6 +455,19 @@ $$a_z = a_{z,\text{base}} \cdot \mu_{\text{heel}} \cdot \mu_{\text{pitch}}$$
|
|
|
351
455
|
* $0.630 \le a_z < 1.250\text{ m/s}^2 \implies \mathbf{Rough}$
|
|
352
456
|
* $a_z \ge 1.250\text{ m/s}^2 \implies \mathbf{Sick}$
|
|
353
457
|
|
|
458
|
+
#### Slatting & Roll-Dampening Penalty (work doc #14)
|
|
459
|
+
|
|
460
|
+
On a monohull, aerodynamic pressure in the sails dampens roll. When
|
|
461
|
+
that pressure is lost but a residual swell remains, the snap-roll and
|
|
462
|
+
boom shock-loading can be worse than a gale — yet raw $a_z$ scores it
|
|
463
|
+
calm, because the sea state itself is modest. After the base rating:
|
|
464
|
+
|
|
465
|
+
1. **Glassy-calm gate:** if $H_{s,\text{combined}} < 0.6\text{ m}$ there is not enough wave energy to roll the hull — the penalty is skipped entirely.
|
|
466
|
+
2. **Dampening-loss thresholds:** otherwise the sails have lost their dampening pressure when
|
|
467
|
+
* upwind / reaching ($|TWA| < 90°$): $TWS < 7.0\text{ kt}$
|
|
468
|
+
* downwind / running ($|TWA| \ge 90°$): $TWS < 12.0\text{ kt}$ — the boat sails away from its wind, dropping apparent wind below flow-attachment.
|
|
469
|
+
3. **Washing-machine penalty:** when the regime is detected, the vertical acceleration is multiplied by $\mu_{\text{slat}} = 2.5$ before ISO banding, and the hour's tier is forced to at least **Rough**. The hourly row carries a `slatting` tag; the tactical dashboard renders slatting-triggered Rough blocks with a diagonal hatch (alternating `--comfort-rough` over the card background) so the skipper sees the discomfort is light-air slatting, not heavy weather — alter course for a better wave angle, drop the main and motor, or lock the boom down.
|
|
470
|
+
|
|
354
471
|
|
|
355
472
|
|
|
356
473
|
### 5.3 Hazard Proximity Ray-Casting Algorithm
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meri-imperiumi/signalk-passage-briefing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Offshore passage daily briefing webapp for Signal K",
|
|
5
5
|
"main": "plugin/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -34,12 +34,19 @@
|
|
|
34
34
|
"@meri-imperiumi/signalk-energy-predictor",
|
|
35
35
|
"@meri-imperiumi/signalk-internet",
|
|
36
36
|
"signalk-polar-management"
|
|
37
|
+
],
|
|
38
|
+
"recommends": [
|
|
39
|
+
"signalk-watch-schedule",
|
|
40
|
+
"signalk-ships-time"
|
|
37
41
|
]
|
|
38
42
|
},
|
|
39
43
|
"engines": {
|
|
40
44
|
"node": ">=22.5.0"
|
|
41
45
|
},
|
|
42
46
|
"dependencies": {
|
|
47
|
+
"@openwaters/maritime-zones": "^0.2.1",
|
|
48
|
+
"astronomy-engine": "^2.1.19",
|
|
49
|
+
"satellite.js": "^7.1.0",
|
|
43
50
|
"yaml": "^2.9.0"
|
|
44
51
|
},
|
|
45
52
|
"devDependencies": {
|