Teach the AI_LM seam which yard an order ships from - #1
Merged
Conversation
AI_LM plans an entire day from ONE depot: DEPOT_LAT/DEPOT_LNG, a single
global config pair. That is fine for a one-yard dealer and quietly wrong
for everyone else. A dealer with two or three branches ships from all of
them on the same morning, so every route whose orders leave from a yard
other than the configured one is optimized from the wrong origin — the
first and last legs are fiction, the drive-time estimate is wrong, and
nothing anywhere says so. This ERP already knows the right answer per
order and simply never told AI_LM.
Phase 1 is the data half of the fix, on this side of the seam only:
* orders.branch_id (NOT NULL since migration 062) now reaches the wire
as `branch_id` on the integration order DTO. Deliberately NOT
omitempty — an order always has a branch, so an empty value is an ERP
bug and AI_LM must be able to see it rather than receive a payload
where "no branch" and "field dropped" are indistinguishable.
* New GET /api/integration/locations returns the dealer's active
branches (id, name, composed address, latitude, longitude), behind
the same X-Integration-Key middleware as its six siblings, as a bare
JSON array like every other list endpoint.
latitude/longitude are *float64 and are NOT COALESCEd in the SQL.
locations.latitude/longitude are backfilled lazily (migration 072,
geocoded the first time a route is optimized from that branch), so
NULL is a real and common state. Collapsing it to 0 would hand AI_LM
a yard at null island — in the Gulf of Guinea — that it cannot tell
apart from a genuine coordinate, which is a worse failure than the
one being fixed. Nil lets AI_LM say "this yard has never been
geocoded, falling back" on the plan.
composeBranchAddress is duplicated from internal/delivery rather than
imported: this package must stay a leaf that httptest can drive
without Postgres. The two must agree, or a branch geocoded through
one path lands somewhere else than the same branch geocoded through
the other, so the rule is unit-tested here.
No migration: 062 and 072 already provide every column this needs.
The cross-repo conformance suite is updated in the same commit, which is
the point of it existing — the endpoint inventory fails on an
unregistered route and Layer A pins the mirror of AI_LM's client structs.
aiLMLocation and aiLMOrder.BranchID are written into the mirror as the
AGREED shape ahead of AI_LM's client.go, because neither repo can merge
half a seam; the mirror header now records that exception explicitly so a
future reader does not mistake it for someone "fixing" the mirror to make
a GableLBM change pass. AI_LM's gable.Location and gable.Order must match
field for field, tag for tag, in the same order.
Consuming this — the BRANCH depot source that outranks config, and the
honest fallback when a day's orders span multiple branches — is Phase 2,
in the AI_LM repo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VS2YVQehtn78L2Cq49TSj3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The ERP half of the branch-depot change. Merge this before
gable-ai-lm#10, which consumes
the endpoint added here.
Why
A dealer with more than one yard ships from several branches on the same day. AI_LM planned
the whole run from one depot (
DEPOT_LAT/DEPOT_LNG), so some routes were silently rooted atthe wrong yard. Gable already knows the answer per order —
orders.branch_idhas beenNOT NULLsince migration 062, andlocations.latitude/longitudehave existed since 072,lazily backfilled by the same OpenRouteService geocoding the delivery service already uses for
resolveBranchOrigin. None of it was reachable across the integration seam.What changed
branch_idon the integration Order DTO — a plainstring, third field, not a pointerand not
omitempty.orders.branch_idisNOT NULL, so an empty string on the wire is anERP bug the consumer should be able to observe rather than something that quietly reads as
"no branch."
New
GET /api/integration/locations— the dealer's branches:id,name,address,latitude,longitude. SameX-Integration-Keyauth as its six siblings (unconfigured → 503,missing/wrong key → 401), bare JSON array, never
null.Two details that are load-bearing for the consumer:
the keys entirely.
nilmeans "never geocoded — do not root a route here"; it is not 0,0.AI_LM's fallback logic depends on being able to tell those apart.
addressis composed by the same rule asdelivery.composeBranchAddress, so a branchgeocoded via either path resolves to the same point.
namefalls back tolocations.codewhen the name is null/empty, so it is never blank.
Rows returned are
type='BRANCH' AND parent_id IS NULL AND active = TRUE. An order whosebranch_idhas no match in that list is "branch unknown" to the consumer, which falls backrather than failing.
No migration. 062 and 072 already provide everything.
Contract suite
ailm_contract_test.goand its goldens are the only thing keeping the two repos in step, sothey are updated in the same change: the endpoint inventory now pins seven routes, the
Orderfixture carriesbranch_id, andailm_locations.jsonis added. Goldens wereregenerated with
-update-goldenand the diff reviewed — only the three intended changes moved.One deviation worth flagging: the mirror structs in that file are normally copied verbatim
from AI_LM. Here they were written ahead of it, because neither repo can merge half a seam.
The mirror header now documents that as an explicit, narrow exception. Adversarial review
confirmed the two sides match field-for-field, tag-for-tag, order-for-order.
Known gaps (pre-existing, not introduced here)
type='BRANCH'predicate, theCOALESCE(NULLIF(name,''), code)fallback,branch_id::text— is executed by no test. Thereis no DB-backed test infrastructure in this repo at all. Column names were hand-verified
against the migrations.
composeBranchAddressis now duplicated betweeninternal/integrationsandinternal/delivery, byte-identical but bound only by a comment. A future edit to one driftssilently.
Gates
gofmt·go vet·go build·go test -race ./...— 50 packages ok, including theupdated contract suite. No migration, no new dependency, no change to
cmd/server/main.go(the handler block was already registered and
/api/integration/is already onPublicPaths,so only
X-Integration-Keygates it — verified by the inventory test).