Cargo Routes - Complete User Guide
Cargo Routes turns the raw AIS voyage history of the whole tracked dry-bulk fleet into a ranked map of real trade flows. It answers the broker/charterer/owner question "where is cargo actually moving, on what ships, how fast, and at what implied cost?" by aggregating every laden port-to-port leg into origin→destination lanes and scoring each lane on volume, tonne-miles, transit, turnaround, fleet composition and week-over-week momentum.
What the page measures, and the macro-collapse trick behind it
Each row is a cargo route (trade lane): a single laden journey from one cargo event (load port) to the next cargo event (discharge port). The intro banner at the top states the rule, and the backend (cargo_routes() + _macro_legs_for_ship()) enforces it precisely:
- Only laden legs count. The engine keeps only macro-legs where
load == 'Laden', so empty ballast repositioning hops are excluded and never pollute the trade picture. We also drop any leg whose loaded draught sits too shallow against the ship’s registry (loaded) draft - that is a vessel sailing in ballast or part-ballast (safra), so a mis-classified “laden” hop, such as a Capesize that appears to carry only a token part-cargo, never counts as a cargo route. - Bunker / canal / transit stops are collapsed. A Capesize that loads in Tubarão, bunkers in Singapore and discharges in Qingdao is counted once as Tubarão → Qingdao. A port stop only ends a macro-leg when the ship's load state changes (Ballast↔Laden, inferred from draught); bunkering, Suez/Panama/Malacca-Singapore waiting and brief technical calls are merged through, with their sea-days, wait-days and berth-days summed into the one journey.
- Partial discharges are not double-counted. A subtle guard (
_loaded_origin) drops laden legs whose origin was a discharge port rather than a real load (the ship arrived laden, dropped some cargo, sailed on still laden). Without this, one cargo discharged across several ports would have masqueraded as fake intra-region trade between those ports. - The loaded-draught floor. A leg only counts as laden if the ship is riding deep enough at one end to be genuinely carrying cargo. A vessel that deballasted before loading rides light at both ends, so it is read as ballast positioning, not a cargo voyage.
- ETA re-declarations are collapsed. The position feed reports a sailing ship's declared next port, which crews re-point while underway (Qingdao one day, Rizhao the next, with the draught unchanged). A port counts as a real call only when the ship is physically there (moored / at anchor), so a loaded vessel re-aiming its ETA no longer fabricates a phantom port-to-port lane.
- Anchorages fold into their parent port (
_anchorage_parent_map), so "Liverpool Anchorage → X" and "Liverpool → X" don't split into two phantom lanes. - Pure hubs are dropped as endpoints. If either origin or destination is a bunker/canal-transit hub (
_NONLOAD_PORTS), the lane is discarded. - Same-country, very short hops are dropped as in-port shuffles, removing coastal repositioning noise.
- Export-complex shuffles are dropped. A laden lane whose origin is a pure-load export terminal (iron ore / coal / bauxite) and whose destination is in the SAME country is a berth or anchorage shift inside the load complex, not a trade - an export terminal never delivers dry bulk to its own country (Tubarão → Vitória). Dropped.
- Intra-import VLOC repositioning is dropped. A Capesize / VLOC with BOTH ends in the Far East basin (China, Japan and Korea all import ore and coal - they never ship it between their own ports on a Cape) is anchorage repositioning or an ETA re-point (Qingdao → Rizhao), not a cargo trade. Indonesian domestic coal sits in region SE Asia, so genuine coastal trade is untouched.
- Tonnage = the ship's DWT, used as a cargo proxy. The page doesn't know manifest tons, so it sums vessel deadweight (dry-bulk usually fills capacity on a laden leg). Every "tonnage" figure is summed DWT, not a cargo manifest.
Why it matters: because the noise is stripped out, what remains is a clean read of genuine load→discharge demand - real trade direction and intensity, not raw port-call churn.
Two views by role. The full live board (every lane, the multi-port voyages tab, Route-Index enrichment) is an internal/admin tool. Members see a focused filtered view: choose a region, a tonnage band and (optionally) a date range, and the page returns just the matching load→discharge lanes - a slice for your criteria, not the whole fleet. Same engine and definitions; only the scope differs. Every row carries a Details link that steps into that lane’s own history page - freight index, the ships that ran it, and its seasonal rhythm - the same per-lane drill-in the full board offers.
Call classification is one rule across both feeds. The draught change at a call, converted to tonnes with the ship’s TPC, must be large enough relative to her size to count as a cargo operation (a smaller change reads as a bunker call); tiny draught blips in broker-reported stays are treated as noise. Historic legs were re-derived under the same rule, so route tonnages are consistent over the whole archive.
Coverage rule (2026-07). Routes are derived from live AIS inside its own tracking eras and from broker-reported port calls outside them (pre-tracking, reception holes, post-AIS). Broker calls only become route endpoints when the ship physically reached the port (moored/anchored evidence) - a declared ETA never mints a route on its own.
Discharge ports are never booked as origins (2026-07). If a ship arrives somewhere laden, discharges, and sails onward empty before reloading, that first port is a discharge, not a load - the route is attributed to where she actually reloaded, never to the discharge port. A leg that leaves a port in ballast (light draught) after arriving laden is treated as a repositioning leg, so no phantom “discharge-port → next-port” cargo lane is created.
The filter bar - narrowing the universe of voyages (server-side)
The grey strip under the intro is the fetch filter bar. Changing any of these and clicking Apply re-queries /api/cargo_routes and rebuilds the whole page:
- From / To (date pickers). Explicit calendar bounds. Setting either one overrides the Window preset chips below.
Fromis the current-window start;Todefaults to today. Only macro-legs whose departure falls in that range feed the displayed metrics. An explicit deepFromcan even push the scan floor back beyond the normal ~820-day history cap. - DWT min / max (two narrow numeric boxes). Bound vessel deadweight. The filter applies after the macro-collapse, so it describes the actual cargo vessel, not its bunker stops. Use it to isolate a size class - e.g.
min 100000for Capesize iron-ore/coal lanes, or65000-82000for Panamax/Kamsarmax. - Segments (csv) (placeholder
Bulk Carrier, MPP, OHBS). A free-text comma list matched against each voyage's ship segment; only lanes whose voyages include those segments survive. The handler caps this at 40 segments. - Search port / country / segment (the 🔍 box, far right of this bar). A second, in-memory search. Unlike the fetch filters it does not re-query - on every keystroke it instantly filters the already-loaded rows by origin/destination name, locode, country or region, or top segment, and it drives the same render path as the table search lower down.
- Row-count label (auto-right,
#fcount). On load it reads e.g.342 rows shown of 770 total(visible vs distinct lanes the server found); once you type in a search box it switches toN rows shown of M loaded(visible vs the rows actually delivered to the browser).
How to use it: set hard scope (date / DWT / segment) with the fetch filters, then use the instant search to pinpoint a port or country without a reload. Note: the page deliberately requests the full set (limit=20000, the server's hard ceiling), so nothing below that ceiling is silently truncated.
The member view - load region + tonnage, optional discharge region, and searching results
Members without the full admin board land on a simpler, restricted Cargo Routes view at the same /cargo-routes URL: pick a Region and a Tonnage band (both required; a discharge region and a date range are optional) and press Show routes to see the matching lanes for that slice of the fleet.
The Region filter matches the load (departure) side only. Selecting e.g. Black Sea shows lanes that loaded cargo in the Black Sea - wherever they discharge - not lanes that merely discharge there after loading somewhere else entirely. This is a deliberate, one-sided match: pick the region you load from, and the destination column is free to show anywhere in the world the cargo actually went.
Optionally narrowing the discharge (destination) side. Alongside the load region you can now also pick a discharge region, or leave it on Any discharge region. Leave it on Any to see everywhere your chosen load region ships to; pick a discharge region to see only the lanes that both load in your chosen region and discharge in that one. The discharge choice is always optional - the load region and tonnage band stay the only required filters, so you can still ask for a load region on its own and get the full picture.
Searching the results (the 🔍 box under the filter bar). Once routes are shown, this box filters the already-loaded rows instantly - no reload - by load or discharge port name/region, the same in-memory pattern the admin board's table search uses. Clear it to see the full filtered set again.
The Window chips - picking your lookback
Below the filter bar is the Window pill row, setting the default scan window when no explicit From/To date is entered: 30d, 90d, 6mo (180d), 12mo (365d, the default, lit in accent), 24mo (730d), and All (3650d).
- The active chip is filled in the accent colour (dark text); the rest are outlined.
- Clicking a chip clears the From/To date pickers so the preset governs, resets the table to page 1, and re-fetches. Typing an explicit From/To date conversely overrides whichever chip is lit.
- The momentum note to the right of the chips (
#momnote) clarifies the Δ column. It readsΔ = voyages this week vs last weekonce any lane carries a delta, orΔ wk/wk: building history (needs 2+ weeks of data)while the system is still warming up.
What to look for: short windows (30/90d) show what is hot right now; long windows (12/24mo, All) reveal the structural trade backbone. A lane that ranks high on 30d but not 12mo is a fresh surge; one that's top on both is a durable artery. Tip: the metrics use the departure date, so very recent days are right-censored - a leg only exists once the ship is seen arriving.
The seven KPI hero cards
The accent-coloured tiles (a 4-across grid, wrapping to a second row) summarise the filtered dataset at a glance. Read them as two groups - the first three cover every ranked lane, the tonne-mile/CO₂ pair cover only the top lanes:
- Distinct routes - count of unique origin→destination lanes the server found (
total; may exceed rows shown). - Total voyages - sum of laden macro-legs across all ranked lanes in the window (not just the visible page).
- Total tonnage (DWT) - summed vessel deadweight across all lanes, shown compactly (
1.24B,880M,540K). The aggregate carrying capacity moved. - Tonne-miles (top lanes) - summed cargo-distance (tonnage carried over the distance sailed), but only over the shown/top lanes (the heavy distance metrics are bounded to the top set, so this is a top-lane subtotal, not a grand total). Tonne-miles is the real freight-demand driver - see the table notes.
- CO₂ tonnes (top lanes) - estimated laden-leg CO₂ summed over the top lanes (derived from modelled fuel burn over the laden days at sea and a standard emission factor). Shown as a plain integer with thousands separators (not compacted).
- Window - the resolved date span, e.g.
2025-06-02 → now, confirming exactly what period the numbers cover. - Data freshness - a coloured badge showing when the system last added a route:
routes updated: 2 h ago(min / h / d ago). It is derived from the newest voyage completion in the dataset (the latest discharge arrival the engine recorded), not the newest load date - because a cargo route only comes into being once a ship is observed arriving at discharge, so a freshly loaded ship still at sea is not yet a route (its load date would otherwise make a perfectly live board look stale). The board is rebuilt every ~6 hours by the derive pipeline, so on a healthy feed this stays green regardless of your filters. Green < 8 h, amber < 24 h, red/pink ≥ a day - a red badge means no voyage has completed in over a day, a likely pipeline stall worth flagging.
Why it matters: the heroes are your sanity check. If "Total voyages" collapses when you tighten a filter, you've over-narrowed. If freshness goes red, treat the most recent lanes cautiously.
The three Top-20 lane bar charts
Beneath the heroes sit three horizontal-bar leaderboards. Each row shows: a lane label (clickable origin and destination port names, each linking to /port/<code>), a small → arrow between them that links to that lane's dedicated history page (/lane?from=…&to=…), an optional segment pill (the lane's top ship segment), a proportional accent-filled bar, and a right-aligned value. Each bar's width is relative to the largest value in that chart, so you read relative throughput instantly. Each list shows up to 20 lanes; an empty filter shows "No data for the selected filter."
- Top 20 trade lanes by voyage count - the page's primary sort, so this is the same order as the underlying data. Value reads e.g.
142 voyages. Your "busiest corridors" view. - Top 20 trade lanes by tonnage (DWT) - re-sorted by summed deadweight. Value reads e.g.
9.20M DWT · 142 vy. Big-ship lanes (Capesize ore/coal) rise here even if voyage counts are modest. - Top 20 trade lanes by tonne-miles - the subtitle flags it as "demand: tonnage carried over the distance it travels, the real freight driver." Only lanes that actually carry a tonne-mile figure are ranked. Value reads e.g.
11.4B t-mi · 142 vy. Long-haul lanes (e.g. Brazil→China) dominate because distance multiplies the tonnage.
How to read them together: a lane high on voyages but low on tonne-miles is short-haul/high-frequency (regional grain or coastal coal); a lane high on tonne-miles but mid-pack on voyages is long-haul ballast-burning demand (the ton-mile-heavy trades that tighten the global market). Comparing the three rankings tells you whether activity on a corridor is about frequency, ship size, or distance.
The All-routes table - the fifteen columns
The full lane table sits at the bottom. Every header is clickable to sort; the sorted column turns accent-coloured. Columns:
- Origin / Destination - port name (links to
/port/<code>) with its locode in grey parentheses. Destination also carries a small↗linking to the lane-history page. These two sort alphabetically (ascending by default). - Voyages - number of laden macro-legs on the lane in the window. The default sort (descending).
- Δ wk/wk - week-over-week momentum: voyages in the last 7 days vs the 7 days before. Both 7-day blocks are anchored behind a short censoring tail (so the still-completing current days don't bias it negative), making the comparison apples-to-apples. Colour-coded: green + rising, pink − falling, grey/ - flat or insufficient history. A leading signal that a corridor is heating up or cooling before it shows in volume.
- Total DWT - summed deadweight on the lane (compact
M/B/K). - Tonne-miles - summed deadweight carried across the route distance. For the top lanes distance is the precise cached sea-route; the long tail uses a great-circle distance with a routing allowance, so the ranking stays complete even past the top set.
- Avg transit (d) - average sea-days per voyage, including merged canal-transit time. How long the laden leg takes at sea.
- Speed (kn) - implied average speed for the lane, read from its distance and its time at sea. A read on whether the lane is being steamed fast (firm market) or slow (eco/soft market).
- Turnaround (d) - average port time (wait + berth days) summed across all stops on the merged voyage. High turnaround signals congestion at the lane's ports.
- Ships - distinct vessels (IMOs) that ran the lane. A high voyage count on few ships means a tight, dedicated rotation; many ships means a broad open trade.
- Top operator - the most active operator on the lane, with its share % in grey. Reveals who controls the corridor.
- Implied $/d - estimated daily hire for the lane, from the live DBPI rate engine (
route_rate), keyed to the lane's DBPI region labels and the vessel band (_pai_band_forthe average DWT) - not the coarse region shown elsewhere in the UI. Computed only for the top lanes; formatted like$18.5k. A market read on what the corridor is worth. - CO₂ (t) - estimated laden-leg emissions for the lane (top lanes only).
- Top segment - the most common ship segment on the lane.
- Last seen - date of the most recent voyage's departure on the lane (the field that also drives the freshness badge).
Sorting behaviour: clicking a header sorts by that field; clicking it again reverses direction. Text columns (origin/destination/segment) start ascending, numeric columns start descending, and any sort change resets to page 1.
Search, pagination and lane drill-down
Above the table is a second toolbar:
- Table search (🔍 Search origin / destination / segment,
#tbl_q). In-memory, instant, and it takes precedence over the top-bar search; whichever box has text filters the same fields (origin/destination name, locode, country, region, top segment). Typing here resets to page 1. - Table count label (
#tbl_count). Reads e.g.1,204 routes - showing 11-20 (page 2 of 121), so you always know your position in the filtered set. - Pagination. Fixed at 10 rows per page (the conventional "block view"). The pager renders as
« Prev 1 … 4 5 6 … 121 Next »with the current page highlighted, a windowed set of numbers around it, and first/last always shown. It hides entirely when the result fits one page. Clicking a page smoothly scrolls the table back into view. - Empty state. If nothing matches, the body shows "No laden cargo voyages match the current filter," the pager hides, and the count reads
0 routes.
Lane drill-down. Both the bar charts and the table link out two ways: a port name opens that port's page (/port/<code>); the →/↗ arrow opens the dedicated lane history page (/lane?from=ORIGIN&to=DEST) for the time-series of that specific corridor. Use the lane page to confirm whether a top-table lane is a steady artery or a one-off spike before you act on it.
A note on completeness vs cost: the cheap metrics (voyages, DWT, transit, ships, operators) are computed for every lane; the expensive ones (precise sea-route distance, implied hire, CO₂) are bounded to the top lanes by voyage count. So a deep-tail lane may show "-" for Implied $/d or CO₂ even though its voyage and tonnage figures are complete - that's by design, not missing data.
Confidence dots. Lane/voyage numbers now carry a small confidence dot - strong (many real voyages), moderate, or thin/modeled - so you can see at a glance how firm each figure is. Thin lanes are indicative and firm up as voyage coverage grows.
The lane page now maps the actual route. For a port-to-port lane it draws a map with the genuine paths recent ships sailed (reconstructed from their own position fixes, so they hug the water and never cross land), over a dashed sea-route reference line, with load/discharge markers. It also shows the total distance (real sea route, great-circle fallback) and a Transit time by vessel class table - how many days each tonnage band actually takes on this lane and the implied average speed. Actual tracks only exist for voyages still inside the position-retention window; older ones fall back to the sea-route line, and the set deepens as history accrues. The page also shows a Currently sailing this lane layer - ships in transit right now (laden, en route to the discharge port per their broadcast destination), drawn in amber with a live position, path-so-far and dashed projection. This is a leading signal that turns into a completed voyage on arrival; it is kept separate from the historical counts, never blended.
The Season column
Each lane row now carries a tiny 12-bar Season strip - departures by month of year, January to December, with the busiest month at full height (hover for exact counts). It answers at a glance whether a trade is steady year-round or pulses with a crop / weather season, and it keeps deepening as more voyages accrue: the underlying monthly lane statistics are stored permanently, so the strip converges on the lane’s true multi-year rhythm rather than a single season’s noise.
Multi-discharge voyages
When a ship carries one cargo and part-discharges it at several ports down a coast, that is one voyage - not separate port-to-port lanes. The page reconstructs these from the ship's full leg chain and shows them as a single load → discharge ports entry, with the number of voyages, distinct ships and the operators that ran each one.
A second or third ‘load’ on a voyage is almost always a nearby port or an anchorage-region name (for example a mid-ocean anchorage code), not a genuine second loading - so we fold it into the first load and the voyage appears as a normal first-load→discharge lane in the routes table above, rather than a phantom multi-load. There is no per-port tonnage split - the AIS/draught feed isn't reliable enough to say how much was lifted at each call, so we show which ports, not how much.
How load ports are resolved - anchorages fold to the real berth
A bulker often loads while sitting at an anchorage (a staging area offshore), and the AIS geofence records that anchorage, not the commercial berth it serves. Left raw, a cargo would look like it loaded at “Campha Anchorage” instead of Campha. We fix this at the source: when a voyage loads at an anchorage, the cargo’s recorded load port (its origin) is lifted to the real berth the anchorage serves - matched first by the anchorage’s own name (“Campha, VN Anchorage” → Campha) and, failing that, by snapping to the nearest commercial berth a short distance offshore. The ship’s physical departure point is still kept truthfully (it really did sail from the anchorage), but every route, lane and origin you see attributes the cargo to the berth - so the All-routes table reads “Campha → ...”, not the anchorage. A small number of genuinely offshore load points (transshipment anchorages with no berth nearby, e.g. some Indonesian bauxite or Thai gypsum anchorage loadings) are left as-is, because there the anchorage is the real load point. The real city of Anchorage, Alaska is never touched.
The Route Index (RAI) - a route’s world-trade activity by tonnage
Every lane in the All-routes table now carries a Route Index (RAI) - the route analog of the Port Activity Index, and the same figure headlines each lane’s own page (open any route from the table or globe). It scores, on a 0-100 scale, how important and efficient a trade lane is in global dry-bulk trade for its tonnage class. 50 = a typical lane for that band; higher = busier and leaner, lower = quieter. The small pill beside the number is the DWT band the lane is scored in, and the confidence dot shows how much voyage evidence sits behind it (thin / moderate / deep).
It blends three things, each measured against the same-band universe of lanes (so a Capesize iron-ore run is never judged against a Handysize coastal hop):
- World-trade frequency - how active the lane is, judged against what is typical for its band.
- World-trade weight - the lane’s tonne-miles (the tonnage it moves over the distance it travels), judged against what is typical for its band, so a long-haul bulk corridor that moves real tonnage outranks a short, thin hop.
- Voyage efficiency - how lean the round is: faster sea passage and lower port turnaround score higher.
The published 0-100 number is a within-band percentile - a lane in the 90th percentile of its band sits in the top 10% most active routes of that size. A separate raw index, scaled so a band-typical lane reads as the neutral baseline, is stored underneath so the value can be tracked as an absolute series over time. RAI is a pure read of observed voyages - it does not use freight price (the Index $/d column beside it is the separate $/day anchor), so it stays a clean measure of activity, not a restatement of rate. It is computed over the same window as the board and saved to an append-only daily history, so - like the freight index - it deepens into a genuine record of how each corridor’s standing in world trade shifts over time.