Skip to content

feat(marine): port directory and marine-fuel history tools; canonical latest path (#125) - #175

Merged
karlwaldman merged 4 commits into
mainfrom
feat/125-marine
Oct 17, 2026
Merged

karlwaldman merged 4 commits into
mainfrom
feat/125-marine

Conversation

@karlwaldman

Copy link
Copy Markdown
Member

Part of #125 (selection 3 of the #77 capability ledger). Ships three of the four ledger rows plus the canonical-path move; GET /v1/prices/marine-fuels stays selected and not exposed (see "Not in this PR").

What changes

Tool REST Notes
opa_get_marine_ports (new) GET /v1/marine-ports, GET /v1/marine-ports/{port_code} Directory with region / country / major_only filters, or one port by UN/LOCODE or name with prices collected in the last 24 h. A 404 names the unknown port and points at the directory, never the plan.
opa_get_marine_fuel_history (new) GET /v1/prices/marine-fuels/historical port_code (5-letter UN/LOCODE), fuel_type enum VLSFO/MGO_05S/HFO_380/HFO_180, days 1-365 (default 30). One row per assessment date (latest collection kept), high/low when currency and unit agree, explicit note when the earliest row is later than the requested window start.
opa_get_marine_fuels now GET /v1/prices/marine-fuels/latest?limit=100 Was the deprecated alias /v1/marine-fuels/latest. limit=100 is the API cap; default is 50 series rows.

Ledger ledgerVersion 3 -> 4 with a changes entry: the three rows flip to exposed, the via alias is dropped from the latest row, the family lists all three tools. Release notes should link ledger v4.

Backend contract (oilpriceapi-api origin/main, read-only)

  • /v1/marine-ports* (V1::MarineFuelPortsController): authenticated, no feature gate; Rack::Attack marine-fuel/api throttle 30/min per token.
  • /v1/prices/marine-fuels/historical (V1::PricesController#marine_fuels_historical): 403 {"error":"Historical marine fuel data requires a paid subscription"} when plan == 'free' and no live trial; port_code + fuel_type required (400 otherwise); days clamped 1-365; limit(1000) rows; Rack::Attack marine-fuel/historical 5/min per token.
  • Not gated on :marine_bunker_data. That feature gates /v1/bunker-fuels/* and /v1/maritime-fuels/*, not these routes.
  • Descriptions state the plan boundary ("paid subscription or an active trial; other accounts get HTTP 403") and that history is rate-limited more tightly and row-capped. They give no numbers, because smoke:public-claims rejects hard-coded rate and plan claims. The numbers are above.

Two backend defects found while verifying:

  • Bare fuel_type=VLSFO on history returns an empty 200. The tool sends the series code VLSFO_SGSIN_USD, which matches exactly today and still works after a backend fix. Re-verified on OilpriceAPI/oilpriceapi-api#7774.
  • days=365 returns 1,000 rows covering only 68 dates, because the cap is spent on repeated collections. metadata.count counts ports, not rows. Filed OilpriceAPI/oilpriceapi-api#9641. The tool de-duplicates per date and says when the window was cut short.

Red / green

Checks run locally

Live verification (production, smoke key, 2026-10-15)

scripts/live-smoke.mjs with the real handlers: PASSED for opa_get_marine_ports, opa_get_marine_fuel_history and opa_get_marine_fuels (HTTP 200, formatter fields present). The runner's only failures are the write-contract tools it skips without SMOKE_WRITE_CONTRACTS=1.

Direct handler calls against production:

  • opa_get_marine_ports {}: 8 ports (SGSIN, NLRTM, USHOU, AEFUJ, HKHKG, USLAX, BRSSZ, USNYC)
  • opa_get_marine_ports {port:"Singapore"}: SGSIN detail and 3 prices from the last 24 h
  • opa_get_marine_ports {port:"XXXXX"}: The API has no marine port "XXXXX" (HTTP 404)…
  • opa_get_marine_fuel_history SGSIN VLSFO 7: 5 dates, High 917 / Low 889.5 USD metric_ton
  • opa_get_marine_fuel_history SGSIN VLSFO 365: 68 dates from 1000 rows, with the earliest-assessment note (2026-07-27 vs 2025-10-15)
  • opa_get_marine_fuel_history SGSIN HFO_180 30: empty, named as such and pointed at fuel services, no plan wording
  • opa_get_marine_fuels {port:"NLRTM"} on the canonical path: 3 prices

Unverified: the live 403 for a free account without a trial. No free key was available. That case is covered by reading the backend code and by a unit test on the API's verbatim error body.

Not in this PR

  • GET /v1/prices/marine-fuels stays selected. Live it is a paginated feed of the 100 most recent collections: 62 rows across 8 ports, several per fuel. That overlaps latest (one per series) and history (per-port series), so a separate tool adds little. It needs a decision: reclassify it as deferred/alias in the ledger, or expose it as a follow-up.
  • The remote energy_marine_fuels facade (src/energyPlugin.ts) still calls the /v1/marine-fuels/latest alias. It is a separate surface and was left unchanged.

🤖 Generated with Claude Code

… latest path (#125)

- opa_get_marine_ports: GET /v1/marine-ports (region/country/major filters)
  and GET /v1/marine-ports/{code} with prices collected in the last 24h.
  404 names the unknown port, never the plan.
- opa_get_marine_fuel_history: GET /v1/prices/marine-fuels/historical for
  one port and fuel, one row per assessment date (latest collection kept),
  high/low, and an explicit note when rows stop short of the requested
  window. Sends the full series code because a bare fuel_type returns an
  empty 200 (oilpriceapi-api#7774). 403 surfaces the API's own text.
- opa_get_marine_fuels moves from the deprecated /v1/marine-fuels/latest
  alias to /v1/prices/marine-fuels/latest?limit=100.
- Ledger v4, live-contract catalog entries, registry categories, README.

GET /v1/prices/marine-fuels stays selected and not exposed.

Part of #125

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ead0cafb-a468-4f72-ac76-7b0a16a983df
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@karlwaldman karlwaldman added the risk:elevated Elevated: auto-merges after an adversarial review APPROVE label Oct 15, 2026
@karlwaldman

Copy link
Copy Markdown
Member Author

Reviewed canonical latest path, port directory and single-port behavior, daily history deduplication, bounded dates, entitlement and empty-data recovery. Corrected the obsolete 24-hour claim to the deployed seven-day observation window and added observed date/age/stale fields with a weekend regression test. Preserved futures-history integration and exact inventory of 40 registered / 36 read-only tools. Validation: 643 tests, build and packed protocol checks, pai-check TypeScript, and exact-provenance non-root Docker smoke; all current CI checks green.

@karlwaldman karlwaldman added risk:reviewed Elevated, adversarial review approved this head SHA and removed risk:elevated Elevated: auto-merges after an adversarial review APPROVE labels Oct 17, 2026
@karlwaldman
karlwaldman merged commit 6b8e106 into main Oct 17, 2026
9 checks passed
@karlwaldman
karlwaldman deleted the feat/125-marine branch October 17, 2026 22:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:reviewed Elevated, adversarial review approved this head SHA

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant