Skip to content

fix(route-explorer): close Asia→DE lane gap for TW/KR/JP/VN/TH/PH/IN + lock down#3832

Merged
koala73 merged 3 commits into
mainfrom
fix/asian-port-clusters-de-lane
May 19, 2026
Merged

fix(route-explorer): close Asia→DE lane gap for TW/KR/JP/VN/TH/PH/IN + lock down#3832
koala73 merged 3 commits into
mainfrom
fix/asian-port-clusters-de-lane

Conversation

@koala73

@koala73 koala73 commented May 19, 2026

Copy link
Copy Markdown
Owner

Summary

  • PR fix(premium): fan out ALL PRO-gated loaders on Free→Pro + close supply-chain race #3828 fixed HK→Germany by adding china-europe-suez + asia-europe-cape to HK in scripts/shared/country-port-clusters.json, but explicitly deferred TW/KR/JP/VN/TH/PH ("each needed review against actual shipping data"). While triaging a reporter's pre-deploy screenshots (HK→DE "No modeled lane", empty WM Analyst, empty Premium Stock), I confirmed those six countries plus IN still resolve to noModeledLane: true against DE — same root cause as HK, same fix shape.
  • Fix: add china-europe-suez + asia-europe-cape (trunk Asia↔Europe container routes terminating at Rotterdam, status: 'active' in src/config/trade-routes.ts) to TW, KR, JP, VN, TH, PH, IN.
  • Lock-down: tests/country-port-clusters-asia-europe-lane.test.mts (3 cases) — data invariant on the cluster JSON, computed-lane assertion for the exact reporter shape (HK→DE/85/container), and full Asian-port matrix against DE.

Why this needs to ship even though the reporter's HK→DE will fix itself

The reporter's screenshots predate today's deploy of PRs #3814 + #3828, so HK→DE will already work for them after this morning's deploy (no client action needed). This PR:

  1. Extends the same fix to the other seven Asian export hubs that PR fix(premium): fan out ALL PRO-gated loaders on Free→Pro + close supply-chain race #3828 deferred — anyone running TW/KR/JP/VN/TH/PH/IN → DE today still hits the dead-end.
  2. Adds the regression test that PR fix(premium): fan out ALL PRO-gated loaders on Free→Pro + close supply-chain race #3828 didn't include — a future contributor removing any of these entries (or PR fix(premium): fan out ALL PRO-gated loaders on Free→Pro + close supply-chain race #3828's HK entry) gets a CI failure naming the exact offender.

Bite-test of the regression test

With this PR's data fix reverted (git stash on the JSON), the test fires:

TW → DE: noModeledLane=true
JP → DE: noModeledLane=true
KR → DE: noModeledLane=true
TH → DE: noModeledLane=true
VN → DE: noModeledLane=true
PH → DE: noModeledLane=true
IN → DE: noModeledLane=true

HK stays green (already fixed by #3828). Confirms each assertion bites the right regression.

Test plan

…+ lock down

PR #3828 fixed HK→Germany by adding `china-europe-suez` and `asia-europe-cape`
to HK's `nearestRouteIds` in `scripts/shared/country-port-clusters.json`, and
explicitly deferred TW/KR/JP/VN/TH/PH because each needed review against actual
shipping data. While triaging a reporter's HK→DE / Premium Stock / WM Analyst
empty-state screenshots (pr-3718 session), I confirmed those six countries plus
IN still fail with `noModeledLane: true` against DE — server reports the gap
honestly, UI shows "No modeled lane for this pair."

Root cause for each is identical to HK's: the country has only
Pacific/intra-Asia/Gulf-oil route IDs, none of which appear in DE's cluster
(`china-europe-suez`, `asia-europe-cape`, `transatlantic`). Server intersection
at server/worldmonitor/supply-chain/v1/get-route-explorer-lane.ts:233-234
returns zero shared routes → noModeledLane=true.

Fix: add `china-europe-suez` and `asia-europe-cape` (the trunk Asia↔Europe
container routes terminating at Rotterdam) to TW, KR, JP, VN, TH, PH, IN.
Both routes are tagged `category: 'container', status: 'active'` in
src/config/trade-routes.ts; they are factually correct for every major Asian
export hub shipping to Northern Europe.

Lock-down: tests/country-port-clusters-asia-europe-lane.test.mts (3 cases)

  1. Data invariant — every Asian port country in {CN,HK,TW,JP,KR,SG,MY,ID,
     TH,VN,PH,IN} must have a non-empty `nearestRouteIds` intersection with
     DE in the cluster JSON. Reverting any of the seven new entries flips
     this test red with a clear "add china-europe-suez and/or asia-europe-
     cape to <ISO2>'s nearestRouteIds" error.
  2. Computed lane — `computeLane('HK','DE','85','container')` must return
     `noModeledLane: false` and a non-empty `primaryRouteId`. This is the
     EXACT shape of the reporter's screenshot (HK→Germany, Electrical &
     Electronics HS2=85, Container, AUTO badge); the original PR #3828 had
     no test pinning this case, so a future contributor reverting the HK
     entry would not be caught.
  3. Full Asian-port matrix — same `noModeledLane: false` assertion for
     all 12 Asian export hubs against DE, defending the algorithm boundary
     (computeLane) on top of the data invariant.

Bite-test: reverting just the data change (git stash on the JSON) flips
tests #1 and #3 red, naming the exact 7 offenders (TW/JP/KR/TH/VN/PH/IN);
test #2 stays green because HK was already fixed by #3828. Confirms each
assertion bites its intended regression.

The 30-query smoke matrix in tests/route-explorer-lane.test.mts (38 cases)
stays 38/38 green. tsc clean.

This complements PR #3828's HK fix and clears the deferred-follow-up note
without touching unrelated areas. The reporter's third screenshot (HK→DE
"No modeled lane") will resolve on next deploy regardless of this PR
(PR #3828 already fixed HK); this PR additionally prevents TW/KR/JP/VN/TH/
PH/IN from showing the same dead-end to other Asia-export-hub users, and
makes both fixes regression-proof.
@vercel

vercel Bot commented May 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
worldmonitor Ignored Ignored Preview May 19, 2026 10:10am

Request Review

@greptile-apps

greptile-apps Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Extends the Asia→Germany lane gap fix from PR #3828 (which covered HK) to the seven deferred countries — TW, KR, JP, VN, TH, PH, and IN — by adding china-europe-suez and asia-europe-cape to their nearestRouteIds entries in the cluster JSON, and adds a regression test that locks down the full 12-country Asian export-hub matrix against DE.

  • Both added route IDs (china-europe-suez, asia-europe-cape) are confirmed status: 'active' container routes in src/config/trade-routes.ts, terminating at Rotterdam, so the intersection logic in computeLane will now resolve lanes for all seven countries.
  • The new test file covers the data invariant (JSON intersection), the original reporter's exact query shape (HK→DE/container/HS2=85), and a full matrix run; the bite-test described in the PR confirms each assertion fires on the right offender when the JSON fix is reverted.

Confidence Score: 5/5

Safe to merge — data-only additions to the cluster JSON with an additive regression test; no existing lane pairs are affected.

The JSON changes expand nearestRouteIds for seven countries using two confirmed-active route IDs; set-intersection logic means additions can only open new lane matches, never break existing ones. The test additions are purely additive and the PR author's bite-test confirms each assertion targets the correct offender.

No files require special attention; the two observations flagged are informational and do not affect correctness.

Important Files Changed

Filename Overview
scripts/shared/country-port-clusters.json Adds china-europe-suez and asia-europe-cape to IN, TW, JP, KR, TH, VN, PH — both confirmed active container routes in trade-routes.ts — closing the shared-route intersection gap that caused noModeledLane: true for these countries against DE.
tests/country-port-clusters-asia-europe-lane.test.mts New regression test with 3 cases: data-invariant on cluster JSON intersection, HK-specific computeLane assertion, and full 12-country matrix against DE. The HK-specific case (test 2) is fully covered by the matrix case (test 3), but this is a minor redundancy rather than a defect.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["computeLane(fromIso2, toIso2, hs2, cargoType)"]
    B["Load fromCluster.nearestRouteIds"]
    C["Load toCluster.nearestRouteIds"]
    D["sharedRoutes = intersection(fromRoutes, toRoutes)"]
    E{sharedRoutes.length === 0?}
    F["noModeledLane: true\n(was the bug for TW/KR/JP/VN/TH/PH/IN → DE)"]
    G["rankSharedRoutesByCargo(sharedRoutes, cargoType)"]
    H["primaryRouteId = rankedRoutes[0]"]
    I["Return lane result\nnoModeledLane: false"]

    A --> B & C
    B --> D
    C --> D
    D --> E
    E -- yes --> F
    E -- no --> G
    G --> H
    H --> I

    subgraph "Fix: Added to TW/KR/JP/VN/TH/PH/IN"
        J["china-europe-suez (already in DE)"]
        K["asia-europe-cape (already in DE)"]
    end

    D -. "now includes" .-> J & K
Loading

Reviews (1): Last reviewed commit: "fix(route-explorer): close Asia→DE lane ..." | Re-trigger Greptile

Comment on lines +50 to +56
});

it('computeLane(HK → DE, container, HS2=85) returns noModeledLane: false', async () => {
const res = await computeLane({ fromIso2: 'HK', toIso2: 'DE', hs2: '85', cargoType: 'container' }, new Map());
assert.equal(res.noModeledLane, false, 'HK → DE container/electronics must resolve a primary route (regression of the user-reported pr-3718 symptom)');
assert.ok(res.primaryRouteId.length > 0, 'primaryRouteId must be populated when noModeledLane is false');
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Redundant HK-specific test case

Test 2 (the standalone HK → DE assertion) is fully exercised by test 3, which iterates over ASIAN_PORT_COUNTRIES — a constant that already includes 'HK' at position 1. Any regression that breaks the HK entry would be caught by both tests, producing two failure messages about the same country. Consider either removing test 2 or replacing the HK-specific assertion with a spot-check of primaryRouteId for all countries (which test 3 currently omits).

"TW": { "nearestRouteIds": ["china-us-west", "intra-asia-container"], "coastSide": "pacific" },
"JP": { "nearestRouteIds": ["gulf-asia-oil", "qatar-asia-lng", "intra-asia-container"], "coastSide": "pacific" },
"KR": { "nearestRouteIds": ["gulf-asia-oil", "qatar-asia-lng", "intra-asia-container"], "coastSide": "pacific" },
"IN": { "nearestRouteIds": ["india-europe", "india-se-asia", "gulf-asia-oil", "china-europe-suez", "asia-europe-cape"], "coastSide": "indian" },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 india-europe remains invisible to DE lane matching

IN already carries india-europe (an active container route from Nhava Sheva to Rotterdam), but DE's cluster does not include india-europe. The intersection logic in computeLane therefore ignores it, and IN→DE will resolve through china-europe-suez / asia-europe-cape instead. This PR's fix is correct and unblocks the lane, but if india-europe is ever meant to be the canonical India↔Europe lane shown in Route Explorer analytics, DE's entry (line 11) would also need india-europe added. Worth a note for whoever owns the route data.

koala73 added 2 commits May 19, 2026 10:25
…urope, not china-europe-suez

Reviewer (correct): adding `china-europe-suez` + `asia-europe-cape` to IN
satisfied `noModeledLane === false` for IN→DE but pushed the resolver to
pick `china-europe-suez` (Shanghai → Rotterdam) as the primary route. The
Route Explorer UI highlights the route's from→to ports, so an Indian
shipment to Germany rendered a Shanghai origin port. The test was vacuous
on this dimension — it only asserted `noModeledLane === false`, never
which route resolved.

Root cause of my original choice: DE's `nearestRouteIds` didn't list
`india-europe`, so no intersection existed for IN→DE via that route. I
papered over the missing intersection by polluting IN's profile with
China-origin trunk routes instead of fixing DE's profile.

Correct fix (follows existing convention): every European country that
already has `china-europe-suez` (the Asia-Europe trunk to Rotterdam) also
gets `india-europe` (the India-Europe trunk to Rotterdam). Both routes
terminate at the same Northern-European hub; the data model treats trunk
routes as broadly serving any European port country. Applied uniformly to
DE, GB, FR, IT, ES, NL, BE, PL, SE, DK, FI, GR, PT (the 13 European
countries with `china-europe-suez`).

IN's `nearestRouteIds` reverts to `["india-europe", "india-se-asia",
"gulf-asia-oil"]` — no more China-origin pollution. IN→DE now intersects
on `india-europe` (only shared route), so the resolver picks it
unambiguously.

Test tightening (the actual lock the reviewer asked for):
`tests/country-port-clusters-asia-europe-lane.test.mts` adds an
`EXPECTED_PRIMARY_ROUTE_TO_DE` map and asserts `computeLane(<iso2>, 'DE',
'85', 'container').primaryRouteId === expected` for all 12 Asian-port
countries. IN→DE must equal `'india-europe'`; all others must equal
`'china-europe-suez'`. Removed the weaker "noModeledLane: false for the
matrix" test — the stricter primaryRouteId assertion subsumes it (a
`noModeledLane=true` response fails the equality check with a clear
message naming the offender).

Bite-test of the new assertion:
  - Revert `india-europe` from DE only → both tests #1 and #3 flip red
    naming IN: data invariant says "IN → DE: shared routes = []", lane
    assertion says "IN → DE: noModeledLane=true (expected
    primaryRouteId='india-europe')". Confirms the assertion bites the
    exact reviewer-flagged regression.
  - HK→DE remains green (china-europe-suez is correct for an HK origin).
  - 38/38 of the existing `tests/route-explorer-lane.test.mts` smoke
    matrix stays green. tsc clean.

The other Asian origins (TW/JP/KR/VN/TH/PH/SG/MY/ID) already correctly
intersect DE on `china-europe-suez` (added in this PR's first commit) —
that's the trunk route their containers actually take to Europe, so
their primaryRouteId assertion was already correct.
…w P2)

Greptile P2: the standalone HK → DE test was fully subsumed by the matrix
test, which iterates over ASIAN_PORT_COUNTRIES (includes HK at index 1)
and asserts `primaryRouteId === EXPECTED_PRIMARY_ROUTE_TO_DE[iso2]`.
HK's expected value is 'china-europe-suez'; a regression on HK fails the
equality check with a clear message, just like every other origin.

Renamed the surviving matrix test to call out HK→DE explicitly so the
provenance trail to the pr-3718 reporter symptom isn't lost.
@koala73
koala73 merged commit 4a3ef58 into main May 19, 2026
11 checks passed
@koala73
koala73 deleted the fix/asian-port-clusters-de-lane branch June 25, 2026 14:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant