Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
142 commits
Select commit Hold shift + click to select a range
b6b9141
fix(ors-control-app): auto-refresh region dropdown after region build
sfc-gh-obielov May 20, 2026
715ee3d
feat(backload-matching): add map and badge colour legend
sfc-gh-obielov May 20, 2026
fe7a2ed
fix(build-routing-solution): logout uses POST form to avoid 405 from …
sfc-gh-obielov May 20, 2026
cd0b169
feat(retail-catchment): multi-region support with boundary-based filt…
sfc-gh-obielov May 20, 2026
6a7051a
fix(backload-matching): make empty deadhead legs visible over VROOM p…
sfc-gh-obielov May 20, 2026
7f77520
fix(build-routing-solution): logout clears SPCS ingress cookies (404 …
sfc-gh-obielov May 20, 2026
beec37b
fix(backload-matching): grey empty legs + click-to-focus assignment +…
sfc-gh-obielov May 20, 2026
5cb4847
refactor(ors_control_app): consolidate map fitting into shared mapFit…
sfc-gh-obielov May 20, 2026
6656359
feat(build-routing-solution): add studio jobs SPCS pipeline with JOB_…
sfc-gh-obielov May 20, 2026
1e0def7
docs(logs): add friction logs from 2026-05-19 runs
sfc-gh-obielov May 20, 2026
c380eca
chore(build-routing-solution): remove broken Logout button (SPCS ingr…
sfc-gh-obielov May 20, 2026
3425fd3
fix(function-tester): fit map to selected region preset when result h…
sfc-gh-obielov May 20, 2026
432da4f
fix(studio/fleet): stratified ghost-trailer selection by spatial bin
sfc-gh-obielov May 20, 2026
c250001
chore(ors_control_app): bump image to v1.1.16 (function-tester deck.g…
sfc-gh-obielov May 20, 2026
3abdddf
feat(shared): fit map once on first load + add Recenter button
sfc-gh-obielov May 20, 2026
733c7e2
feat(route-optimization): auto-seed PLACES out of the box for any region
sfc-gh-obielov May 20, 2026
5be3484
fix(route-optimization): dynamic profile dropdown per region
sfc-gh-obielov May 20, 2026
7c02ecb
fix(ors_control_app): correct map recenter for H3 hexagon layers
sfc-gh-obielov May 21, 2026
8e968fd
chore(ors_control_app): bump image to v1.1.19 for hex recenter fix
sfc-gh-obielov May 21, 2026
a6cba06
fix(ors_control_app): re-fit MatrixViewer camera when switching matrices
sfc-gh-obielov May 21, 2026
24a1986
chore(ors_control_app): align image-versions.env with deployed v1.1.22
sfc-gh-obielov May 21, 2026
c34c2e1
fix(ors_control_app): widen MatrixViewer fitKey to handle two-phase load
sfc-gh-obielov May 21, 2026
ef63461
fix(ors_control_app): animate setViewState and guard onViewStateChang…
sfc-gh-obielov May 21, 2026
79c5f79
fix(build-routing-solution): swallow ambiguous-column error in studio…
sfc-gh-obielov May 21, 2026
6f8be50
feat(backload-matching): add force-shared-destination toggle for visu…
sfc-gh-obielov May 22, 2026
d1b3bad
feat(route-optimization): port full VRP UI from SUMMIT onto current s…
sfc-gh-obielov May 22, 2026
46eb462
feat(route-optimization): extend LOOKUP/JOB_TEMPLATE schema for depot…
sfc-gh-obielov May 22, 2026
e918eb0
fix(backload-matching): auto-fill shared destination from first trailer
sfc-gh-obielov May 22, 2026
69ee05c
feat(ors_control_app): region-agnostic spatial spread for synthetic data
sfc-gh-obielov May 22, 2026
dbb756b
feat(backload-matching): spread converging routes via PathLayer later…
sfc-gh-obielov May 22, 2026
1da8c35
fix(backload-matching): revert lateral offset, use translucent blend …
sfc-gh-obielov May 22, 2026
313e274
feat(routing-agent): add agent-demos.json and /api/agent/config endpoint
sfc-gh-obielov May 22, 2026
b91abe4
feat(routing-agent): switch backend to Cortex Agent REST API with wor…
sfc-gh-obielov May 22, 2026
740fdd5
feat(routing-agent): add pharma supply-chain tools (additive, multi-r…
sfc-gh-obielov May 22, 2026
a52d561
feat(setup-agent-playground): port skill from SUMMIT for SF pharma de…
sfc-gh-obielov May 22, 2026
ffa70c5
refactor(agent-playground): extend helpers with scenario/savedPrompt/…
sfc-gh-obielov May 22, 2026
02256f1
feat(routing-agent): scenario tabs, saved prompts, workflow panel UI
sfc-gh-obielov May 22, 2026
d56a05f
docs(routing-agent): document pharma tools and add setup-agent-playgr…
sfc-gh-obielov May 22, 2026
d68a2e1
feat(build-routing-solution): own SEED_ROUTE_OPTIMIZATION_REGION proc…
sfc-gh-obielov May 22, 2026
37f66bd
fix(build-routing-solution): log route-opt seed failures to job MESSAGE
sfc-gh-obielov May 22, 2026
cb08e6b
chore(route-optimization): trim seed-data.sql now that proc lives ups…
sfc-gh-obielov May 22, 2026
cad0d63
feat(ors_control_app): /api/route-optimization/ensure-seeded for any …
sfc-gh-obielov May 22, 2026
bfa9ff9
chore(ors_control_app): bump image to v1.1.32 and redeploy SPCS service
sfc-gh-obielov May 22, 2026
67c02b9
fix(route-optimization): self-heal page for any region; UX bug fixes
sfc-gh-obielov May 22, 2026
c9feef4
fix(routing-functions): restrict REGION_FOR_POINT to deployed regions
sfc-gh-obielov May 22, 2026
d09db1a
fix(routing-agent): surface real region in service_unreachable errors
sfc-gh-obielov May 22, 2026
4091b95
feat(emergency-response): add skill with bundled SF-clipped IPAWS seed
sfc-gh-obielov May 22, 2026
d702829
fix(build-routing-solution): bump ORS maximum_waypoints to 1000 for V…
sfc-gh-obielov May 22, 2026
8c05782
fix(vrp): bind capacity via job demand, anchor vehicles to depot, hon…
sfc-gh-obielov May 22, 2026
527dfba
fix(routing-agent): render isochrone polygon + POI markers from tool_…
sfc-gh-obielov May 22, 2026
06ce737
feat(emergency-response): one-command install via scripts/install.sh
sfc-gh-obielov May 22, 2026
07276ee
feat(agent-playground): SUMMIT polish — vehicle/skill/risk visuals + …
sfc-gh-obielov May 22, 2026
b521d58
chore(ors_control_app): bump image to v1.1.34 and redeploy SPCS service
sfc-gh-obielov May 22, 2026
3d42ca2
feat(build-routing-solution): raise ORS distance and matrix route limits
sfc-gh-obielov May 22, 2026
ecdf91f
feat(build-routing-solution): support shipments in gateway OPTIMIZATI…
sfc-gh-obielov May 22, 2026
25f50f2
feat(backload-matching): add consolidate mode and detour budget (v1.1…
sfc-gh-obielov May 22, 2026
57bb9a3
feat(emergency-response): wire Emergency Response navigation in contr…
sfc-gh-obielov May 22, 2026
cf2df96
feat(backload-matching): native VROOM/ORS levers + freight economics …
sfc-gh-obielov May 25, 2026
3f14fd2
docs(logs): friction log for backload-matching v1.1.35 build
sfc-gh-obielov May 25, 2026
bc250fd
feat(asset-velocity): smart reposition with ORS Matrix + VROOM constr…
sfc-gh-obielov May 25, 2026
0db0ed9
fix(backload-matching): looser max_distance baseline + drop unsupport…
sfc-gh-obielov May 25, 2026
11763c5
feat(ors_control_app): add PageContainer wrapper for reliable page sc…
sfc-gh-obielov May 25, 2026
5b10a3c
refactor(dwell-analysis): wrap dwell sub-pages in PageContainer
sfc-gh-obielov May 25, 2026
a404b93
refactor(fleet-intelligence): wrap fleet dashboard pages in PageConta…
sfc-gh-obielov May 25, 2026
6691316
refactor(ors_control_app): wrap remaining dashboard pages in PageCont…
sfc-gh-obielov May 25, 2026
34a4ea5
fix(backload-matching): use VROOM-supported costs.per_km and surface …
sfc-gh-obielov May 25, 2026
1262de9
feat(backload-matching): hoverable info tooltips for every lever (v1.…
sfc-gh-obielov May 25, 2026
b2648b1
fix(ors_control_app): escape-proof SQL JSON inlining + visible SQL er…
sfc-gh-obielov May 25, 2026
b458b01
fix(backload-matching): render literal \n as line breaks in InfoTip (…
sfc-gh-obielov May 25, 2026
9ac80be
fix(backload-matching): eliminate "Calling OPTIMIZATION..." multi-min…
sfc-gh-obielov May 25, 2026
45e12fc
chore(ors_control_app): bump to v1.1.40
sfc-gh-obielov May 25, 2026
1c063d1
fix(backload-matching): use single-quote escaping for OPTIMIZATION SQ…
sfc-gh-obielov May 25, 2026
6d2f628
fix(backload-matching): unstack Stops and Recenter map buttons (v1.1.41)
sfc-gh-obielov May 25, 2026
594f15f
feat(backload-matching): sort assignments by NET_BENEFIT_EUR desc
sfc-gh-obielov May 25, 2026
8a86979
fix(backload-matching): deterministic shipment subset + bigger time f…
sfc-gh-obielov May 25, 2026
6e42479
fix(backload-matching): universal envelope-based time/distance budgets
sfc-gh-obielov May 25, 2026
f9ec3cb
fix(backload-matching): unmask gateway errors + raise UI matrix cap
sfc-gh-obielov May 26, 2026
794e67b
fix(backload-matching): dedupe VW_TRAILERS to stop duplicate trailer …
sfc-gh-obielov May 26, 2026
e15265b
feat(backload-matching): per-vehicle empty-leg budgets for detour & d…
sfc-gh-obielov May 26, 2026
e50628d
feat(freight-exchange): extend Data Studio engine for partners + enri…
sfc-gh-obielov May 26, 2026
3ce022d
feat(freight-exchange): scaffold new skill (SKILL.md + references)
sfc-gh-obielov May 26, 2026
5010001
feat(routing-agent): make Agent Playground region+vehicle aware
sfc-gh-obielov May 26, 2026
f8ed524
feat(freight-exchange): add FreightExchange React page (Phase A + B)
sfc-gh-obielov May 26, 2026
557ebda
docs(AGENTS.md): register freight-exchange in inventory + dependency …
sfc-gh-obielov May 26, 2026
f63fd97
feat(backload-matching): numbered stop markers on map matching Stops …
sfc-gh-obielov May 26, 2026
6f31bd7
fix(ors-control-app): use plain $$ dollar-quotes in asSqlJsonLiteral
sfc-gh-obielov May 26, 2026
5b99f77
chore(ors-control-app): bump image tag to v1.2.0
sfc-gh-obielov May 26, 2026
8799bd5
fix(backload-matching): render InfoTip via portal to prevent edge cro…
sfc-gh-obielov May 26, 2026
9548b68
feat(backload-matching): expose payload caps as UI sliders
sfc-gh-obielov May 26, 2026
73d7b3e
fix(studio): pin per-region ORS/VROOM/pool auto_suspend during Data S…
sfc-gh-obielov May 26, 2026
fe8c8e6
fix(reconcile): include GENERATION_JOBS in RECONCILE_AUTO_SUSPEND saf…
sfc-gh-obielov May 26, 2026
4865190
feat(studio): add DIM_DATASETS registry + idempotent backfill
sfc-gh-obielov May 26, 2026
4c2cc09
fix(ors_control_app): coerce numeric/boolean columns from REST API st…
sfc-gh-obielov May 26, 2026
4d7edc9
feat(studio): add V_*_CURRENT projection views for dataset versioning
sfc-gh-obielov May 26, 2026
7befb20
refactor(server): repoint server consumers to V_*_CURRENT views
sfc-gh-obielov May 26, 2026
907594e
refactor(consumers): repoint client + studio routes + route-optimizat…
sfc-gh-obielov May 26, 2026
a191f0d
feat(studio): replace clearRegionScope with non-destructive archivePr…
sfc-gh-obielov May 26, 2026
e656df4
feat(studio): add dataset registry CRUD endpoints
sfc-gh-obielov May 26, 2026
a8c1a15
feat(studio-ui): add Datasets management panel to Data Studio
sfc-gh-obielov May 26, 2026
9618f2f
docs(AGENTS.md): document dataset-versioning contract
sfc-gh-obielov May 26, 2026
ee0f1a5
chore(ors_control_app): bump to v1.1.52 with dataset versioning
sfc-gh-obielov May 26, 2026
7c2ff3a
feat(freight-exchange): add ORS/VROOM enrichment phases E1-E8
sfc-gh-obielov May 26, 2026
24d23e3
feat(backload-matching): raise BM_MAX_MATRIX_LOCATIONS 200 -> 500
sfc-gh-obielov May 26, 2026
12b2f2f
feat(datasets): per-dataset activation from header dropdown + flip-last
sfc-gh-obielov May 26, 2026
d305041
fix(backload-matching): hide empty-leg layer until DIRECTIONS hydrates
sfc-gh-obielov May 26, 2026
4b9615b
refactor(studio): remove dead clearRegionScope() helper
sfc-gh-obielov May 26, 2026
ed11603
fix(backload): seed endpoint returns actionable error when fleet/POIs…
sfc-gh-obielov May 26, 2026
5100bea
fix(backload): show per-table coverage in empty-state hint
sfc-gh-obielov May 26, 2026
a5ba3bf
refactor(freight-exchange): split FreightExchange.tsx into companion …
sfc-gh-obielov May 27, 2026
3cc1b42
fix(build-routing-solution): seed VERSION_INFO table and stop swallow…
sfc-gh-obielov May 27, 2026
262ad90
fix(build-routing-solution): pin routing_gateway_service during regio…
sfc-gh-obielov May 27, 2026
6d9fcad
feat(build-routing-solution): add ORS_REQUEST_LOG observability layer…
sfc-gh-obielov May 27, 2026
9ae56b6
feat(routing-customization): ship validated ors-config.yml presets (#54)
sfc-gh-obielov May 27, 2026
385a7c4
feat(gateway): retry/backoff/circuit-breaker + request-size guardrail…
sfc-gh-obielov May 27, 2026
e90f211
feat(build-routing-solution): graph-build preflight + sizing referenc…
sfc-gh-obielov May 27, 2026
9b579eb
feat(routing): /directions canary after ORS resume (#53)
sfc-gh-obielov May 27, 2026
b930b04
feat(routing): caller-supplied isochrone smoothing + #61 design note
sfc-gh-obielov May 27, 2026
1fb5c2a
feat(region-builder): BuildSummaryCard with LM N/M progress + log tai…
sfc-gh-obielov May 27, 2026
cbfbb20
feat(demos): SQL-native ESTIMATE_MATRIX_COST + VEHICLE_THRESHOLDS tab…
sfc-gh-obielov May 27, 2026
5eefa1f
feat(traffic-calibration): scaffold #64 calibration skill
sfc-gh-obielov May 27, 2026
1b2668e
revert(traffic-calibration): drop #64 scaffold
sfc-gh-obielov May 27, 2026
a03d9ff
fix(audit-pr-120): close 6 resilience + compliance gaps from PR #120 …
sfc-gh-obielov May 27, 2026
a8603ae
fix(observability): 3 bugs in INGEST_ORS_METRICS + bump gateway image…
sfc-gh-obielov May 27, 2026
0fe5de1
feat(gateway): raise matrix guardrail 200 -> 1500 to fit demo workloads
sfc-gh-obielov May 27, 2026
c4e7763
fix(studio): refuse activating a dataset with 0 rows in DIM_FLEET
sfc-gh-obielov May 27, 2026
c5d163f
fix(asset-velocity): wrap MATRIX options in PARSE_JSON and lower defa…
sfc-gh-obielov May 27, 2026
752807e
fix(asset-velocity): surface real VROOM unassigned reason; local-day …
sfc-gh-obielov May 27, 2026
9f1cac1
feat(backload-matching): vehicle-class-agnostic solver via VEHICLE_CL…
sfc-gh-obielov May 27, 2026
f884123
fix(asset-velocity): drive solver from VEHICLE_CLASS_PROFILE so it wo…
sfc-gh-obielov May 27, 2026
50d6b27
fix(build-routing-solution): bootstrap OBSERVABILITY schema on fresh …
sfc-gh-obielov May 27, 2026
92619f0
perf(studio): bump telemetry INSERT batch from 500 to 2000 rows
sfc-gh-obielov May 27, 2026
8160f21
perf(studio): parallelise per-vehicle day generation + mid-day stream…
sfc-gh-obielov May 27, 2026
d018b99
chore(spcs): bump ors_control_app to 1-2 CPU / 1-2 GiB
sfc-gh-obielov May 27, 2026
f3e260a
docs(route-optimization,AGENTS): DIM_DATASETS bootstrap invariant + e…
sfc-gh-obielov May 27, 2026
e90c287
feat(freight-exchange): show selected offer origin, destination, and …
sfc-gh-obielov May 27, 2026
326b0a4
feat(ors-control-app): remount data pages when active Studio dataset …
sfc-gh-obielov May 27, 2026
01d4f86
chore(spcs): bump ors_control_app to v1.1.62
sfc-gh-obielov May 27, 2026
60e16e3
fix(backload-matching): remove client-side UPDATE on region change (4…
sfc-gh-obielov May 27, 2026
284a07f
feat(ors-control-app): default routing demos from active preset
sfc-gh-obielov May 27, 2026
1cc7a51
fix(freight-exchange): correct ORS call shape across /api/fx/*, prese…
sfc-gh-obielov May 27, 2026
76a0ab5
fix(ors-control-app): stop FunctionTester region/profile revert
sfc-gh-obielov May 28, 2026
3297f29
fix(ors_control_app): harden dataset preset activation and picker errors
sfc-gh-obielov May 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions .cortex/skills/backload-matching/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,13 @@ See `references/use-case-narrative.md` for the full story. Summary anchored in t
| SCHEMA | `BACKLOAD_MATCHING` | Schema for backload tables and views |
| WAREHOUSE | `ROUTING_ANALYTICS` | Warehouse for queries |
| REGION | (active preset) | Auto-derived from `BACKLOAD_MATCHING.CONFIG`, which mirrors the active Control App region/vehicle. No hardcoded city. |
| HOME_REGION | `Nordics` | Country group counted as "back-to-home" |
| HOME_LAT / HOME_LON | `55.6759 / 12.5655` | Anchor (Copenhagen) used as vehicle `end` |
| TRAILER_COUNT | `80` | Idle-bound trailers seeded |
| INTERNAL_VOLUMES_COUNT | `120` | Internal waiting loads seeded |
| EXTERNAL_OFFERS_COUNT | `300` | Synthetic external offers seeded |
| VEHICLE_CLASS_PROFILE | `OPENROUTESERVICE_APP.CORE.VEHICLE_CLASS_PROFILE` | Single source of truth for per-vehicle-class capacity (`PAYLOAD_KG_TYP`), shipment-weight band, ORS profile, costs (€/km, €/hr), and UI label. The skill is transport-type agnostic — no HGV-specific constants. Seeded with 8 classes (`bicycle`, `ebike`, `foot`, `motorcycle`, `car`, `van`, `hgv`, `truck`). Unknown `vehicle_type` → bootstrap and React both fail loudly so a custom preset never silently runs with wrong-class defaults. |
| TRAILER_COUNT | up to ~80 (driven by Data Studio dataset) | Idle-bound trailers for the active preset |
| INTERNAL_VOLUMES_COUNT | 120 | Internal waiting loads (most-recent FACT_TRIPS) |
| EXTERNAL_OFFERS_COUNT | 300 | Synthetic external offers per region |
| INTERNAL_PRIORITY | `100` | VROOM `priority` on internal jobs |
| EXTERNAL_PRIORITY | `10` | VROOM `priority` on external offers |
| TIME_WINDOW_TOLERANCE_HRS | `4` | Pickup-window slack added to jobs |
| MAX_EMPTY_KM_PER_LEG | `200` | Hard skip in candidate pre-filter |
| MAX_VEHICLES_PER_SOLVE | `30` | Solver caps vehicles per call to keep ORS responsive |
| EUR_PER_EMPTY_KM | `1.20` | Used for KPI ("EUR/day reclaimed") |
| IDLE_COST_EUR_PER_DAY | `650` | Used for KPI ("EUR/day reclaimed") |
Expand Down
187 changes: 135 additions & 52 deletions .cortex/skills/backload-matching/references/bootstrap.sql

Large diffs are not rendered by default.

158 changes: 100 additions & 58 deletions .cortex/skills/backload-matching/references/optimization-vrp-mapping.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Backload Matching Engine — OPTIMIZATION VRP mapping

This document shows the exact JSON the page sends to `OPENROUTESERVICE_APP.CORE.OPTIMIZATION(...)` and how each field maps to the DHL Freight backload story.
This document shows the exact JSON the page sends to `OPENROUTESERVICE_APP.CORE.OPTIMIZATION(...)` and how each visible UI lever maps 1:1 to a real VROOM or ORS field. The new design has **no JS pre-filters or post-filters** — every knob you turn lands inside the solver.

## Vehicles (one per idle-bound trailer)

Expand All @@ -9,82 +9,124 @@ This document shows the exact JSON the page sends to `OPENROUTESERVICE_APP.CORE.
"id": 1,
"profile": "driving-hgv",
"start": [6.9603, 50.9375],
"end": [12.5655, 55.6759],
"capacity": [24000],
"end": [12.5655, 55.6759], // omit when "Open-ended"
"capacity": [24000, 33, 90], // [kg, pallets, m³] when multi-dim
"skills": [1, 2],
"time_window": [1715874000, 1715917200]
"max_tasks": 2, // Max stops per trailer
"max_travel_time": 28800, // ideal_empty_hrs + slack
"max_distance": 600000, // ideal_empty_km × (1 + dev%)
"costs": { "fixed": 12000, "per_km": 160 },
"time_window": [1715874000, 1715906400], // optional shift
"breaks": [{ "id": 1, "service": 2700,
"time_windows": [[1715890200, 1715895600]] }],
"profile_options": { "avoid_polygons": { "type": "MultiPolygon", ... } }
}
```

| Field | Source | Why |
| Field | UI lever | Why |
|---|---|---|
| `start` | `VW_TRAILERS.DROPOFF_LON / DROPOFF_LAT` | Where the trailer becomes idle |
| `end` | `VW_TRAILERS.HOME_LON / HOME_LAT` | Forces the solver to bias jobs that point home |
| `profile` | `'driving-hgv'` | HGV graph — respects truck restrictions |
| `capacity` | `[VW_TRAILERS.MAX_PAYLOAD_KG]` | Single dimension (mass); add volume / pallets as needed |
| `skills` | `[1, 2]` if non-ADR; `[1, 2, 3]` if `HAZMAT_CERT = TRUE` | Skill `1` = internal, `2` = external, `3` = ADR-only |
| `time_window` | `[ETA_TS_unix, ETA_TS + 12h_unix]` | Trailer becomes available at ETA, expires after 12h shift |

## Jobs (internal volumes + external offers, unioned)
| `end` | radio: Home / Shared / Open | When `Open`, omit the field — solver lets the trailer finish anywhere |
| `profile` | derived from region | ORS routing graph |
| `capacity` | toggle: multi-dim capacity | `[kg]` or `[kg, pallets, m³]` |
| `skills` | HAZMAT cert flag | `[1,2,3]` if certified, `[1,2]` otherwise |
| `max_tasks` | **Max stops per trailer** | 1 = single backload, 2-6 = consolidation |
| `max_travel_time` | **Detour budget** (+h on top of ideal empty trip) | Hard time cap on the whole tour |
| `max_distance` | **Allowed deviation** (+% from ideal empty trip) | Hard distance cap on the whole tour |
| `costs.fixed` | **Fixed dispatch cost** (€) × 100 | Pay-to-add-vehicle penalty |
| `costs.per_km` | (**€/km** + **€/h** ÷ 60 km/h) × 100 | Time + distance weighted into VROOM's per-km cost. Note: the deployed regional VROOM image is `vroom-docker:v1.0.4`, which does NOT yet support `costs.per_hour` (added upstream in VROOM v1.13). When the gateway upgrades to v1.13+, switch back to `per_hour` for cleaner semantics. |
| `time_window` | toggle: shift / hours-of-service | `[shiftStart, shiftEnd]` |
| `breaks` | toggle: enforce driver break | EU 45-min rest after 4.5 h |
| `profile_options.avoid_polygons` | multi-select: avoid zones | Forwarded to ORS routing for LEZ / construction / hazard avoidance |

## Shipments (internal volumes + external offers)

```json
{
"id": 41,
"location": [6.9512, 50.9301],
"service": 1800,
"amount": [12500],
"skills": [1],
"priority": 100,
"time_windows": [[1715874000, 1715901000]]
"pickup": { "id": 41, "location": [...], "service": 1800,
"time_windows": [[t1_from, t1_to], [t2_from, t2_to]] },
"delivery": { "id": 41, "location": [...], "service": 600 },
"amount": [12500, 17, 50],
"skills": [1],
"priority": 90
}
```

| Field | Source | Why |
| Field | UI lever | Why |
|---|---|---|
| `location` | `INTERNAL_VOLUMES.PICKUP_GEOM` or `EXTERNAL_OFFERS.PICKUP_GEOM` | Pickup point |
| `amount` | `WEIGHT_KG` | Must fit in vehicle capacity |
| `skills` | `[1]` for INTERNAL, `[2]` for EXTERNAL, plus `3` if ADR | Gates assignability |
| `priority` | `INTERNAL_PRIORITY=100` for INTERNAL, `EXTERNAL_PRIORITY=10` for EXTERNAL | VROOM unloads jobs in priority order under contention |
| `service` | 1800 sec (30 min) | Loading time at pickup |
| `time_windows` | `[PICKUP_FROM_TS, PICKUP_TO_TS]` (unix) | Hard window |

## The full `OPTIMIZATION(...)` call

The page builds:

```javascript
const challenge = {
jobs: [...internalJobs, ...externalJobs],
vehicles: trailers.map(toVehicle),
options: { g: true } // return geometry for each route
};

const sql = `SELECT * FROM TABLE(OPENROUTESERVICE_APP.CORE.OPTIMIZATION(
PARSE_JSON('${JSON.stringify(challenge)}'), '${regionName}'))`;
```
| `pickup.location` / `delivery.location` | source data | Pickup / dropoff |
| `service` | hard-coded (1800 / 600 sec) | Loading / unloading time |
| `amount` | toggle: multi-dim | `[kg]` or `[kg, pallets, m³]` |
| `skills` | HAZMAT flag on shipment | `[1]` internal, `[2]` external, `+3` if ADR |
| `priority` | **Internal-first weight** (single slider) | Internal = `W`, External = `100 - W` |
| `time_windows` | **Window slack** (±h) and toggle: multi-window | Widened ±slack; second window synthesised at +8 h when toggled |

VROOM returns one row per vehicle with:
## Top-level options
- `options.g = true` — return GeoJSON geometry per route. Always on.

- `VEHICLE` — the vehicle id we sent.
- `STEPS` — ordered jobs assigned (start, job_1, job_2, ..., end).
- `COST` — total travel time in seconds for that vehicle.
- `GEOJSON` — the LineString for the whole route (when `options.g = true`).
- `UNASSIGNED` (top-level row) — jobs the solver could not place (capacity, time-window, skill, or `max_empty_km` exceeded).
## Post-solve economics (computed in the React layer, not VROOM)

| Display column | Formula |
|---|---|
| Tour km | `r.DISTANCE / 1000` (or fallback from duration × speed) |
| Tour hrs | `r.DURATION / 3600` |
| Cost (€) | `dispatch + tourHrs × €/h + tourKm × €/km + nDeliveries × €/delivery` |
| Revenue (€) | external: `PRICE_EUR`. Internal: `loaded_km × €/loaded-km` |
| Net benefit (€) | `Revenue − Cost` (red badge when negative) |
| Wait time | per-step `waiting_time` from VROOM (chip when > 30 min) |

## How DHL's "structural process" maps to VROOM parameters
## What got removed from the old UI

| Customer parameter | VROOM lever |
| Old control | Why it's gone |
|---|---|
| Internal-first | Job `priority`: 100 vs 10 |
| Direction-to-home | Vehicle `end` location set to home depot |
| ADR equipment gating | Skill id `3` on both ADR jobs and ADR-certified vehicles |
| Time-window tolerance | Widen `time_windows` on jobs by `tolerance_hrs` |
| Max empty km per leg | Pre-filter the `jobs[]` array in SQL: `WHERE empty_km <= MAX_EMPTY_KM` |
| Trailer capacity | Vehicle `capacity[0]` |
| Cargo weight | Job `amount[0]` |
| `Max empty km/leg` | Was a JS post-filter that silently dropped solver decisions. Empty km is now minimised directly by `costs.per_hour` + `max_travel_time`. |
| `Max detour km` | Was a JS pre-filter that hid candidates from VROOM. Redundant with `max_distance` (deviation %). |
| Two priority sliders (internal + external) | Collapsed into one **Internal-first weight** slider. |
| Match mode radio (`single` / `consolidate`) | Replaced by **Max stops** number (1 = single, ≥2 = consolidation). |
| `Force shared destination` checkbox | Replaced by the 3-way **Trailer end** radio (Home / Shared / Open). |

## Productisation notes (out of scope for the demo)

- **Real-time refresh**: replace the polled `VW_TRAILERS` with Snowpipe Streaming on a `TELEMETRY_RAW` topic; rebuild the view as a Dynamic Table with a 5-minute target lag.
- **Live freight-exchange feeds**: the four portals (Timocom, WTransnet, Teleroute, B2P) all publish offers via either REST APIs (Timocom Smart Logistics System REST), webhook subscriptions, or paid CSV pulls. A small Snowpark Container Services worker can normalize them into `EXTERNAL_OFFERS` with the same schema.
- **Solver scale**: VROOM solves 200+ vehicles x 1000+ jobs in a few seconds. Beyond that, partition by region (NRW, Bavaria, Île-de-France) and solve in parallel — the customer's existing dispatcher mental model already partitions this way.
- **Live freight-exchange feeds**: the four EU portals (Timocom, WTransnet, Teleroute, B2P) all publish offers via REST APIs or webhook subscriptions. A small Snowpark Container Services worker can normalize them into `EXTERNAL_OFFERS` with the same schema.
- **Solver scale**: VROOM solves 200+ vehicles × 1000+ jobs in a few seconds. Beyond that, partition by region (NRW, Bavaria, Île-de-France) and solve in parallel.
- **Pre-computed matrices**: for repeated what-if solves on the same point set, call `OPENROUTESERVICE_APP.CORE._OPTIMIZATION_TABULAR_RAW(jobs, vehicles, matrices, region)` with a cached matrix. (Cache table not seeded in v1.1.35; follow-up work.)
- **Pinned stops** (`vehicle.steps[]`): dispatcher overrides where a specific stop is locked to a specific trailer. (UI hook not shipped in v1.1.35; follow-up work.)
- **Mixed jobs + shipments**: VROOM accepts both `jobs[]` (single-location) and `shipments[]` (paired). Useful for return / empty-container relocation tasks. (Follow-up.)

## Sanity probe: "OPTIMIZATION returned 0 rows"

If the UI surfaces "OPTIMIZATION returned 0 rows", first verify VROOM itself is
healthy for the region with this minimal probe (replace `$REGION` and the four
coordinates with values that lie inside the region's boundary). One returned
row means VROOM works — the issue is then in the payload your code is
generating (almost always `max_travel_time` or `max_distance` too tight, or an
unknown VROOM v1.0.4 field; see compatibility table above).

```sql
SET REGION = 'Germany';
SELECT VEHICLE FROM TABLE(OPENROUTESERVICE_APP.CORE.OPTIMIZATION(
PARSE_JSON('{
"vehicles":[{"id":1,"profile":"driving-hgv",
"start":[13.4050,52.5200],"end":[11.5820,48.1351],
"capacity":[24000],"max_tasks":3}],
"shipments":[{"pickup":{"id":1001,"location":[8.6821,50.1109],"service":1800},
"delivery":{"id":1001,"location":[9.9937,53.5511],"service":600},
"amount":[10000]}]
}'), $REGION));
```

### Common silent-rejection causes (VROOM v1.0.4)

VROOM v1.0.4 silently drops the entire response (0 rows, no error) when:

- `max_travel_time` is smaller than the shortest feasible tour. The React UI
computes this from the haversine envelope of the candidate pool times
`(2 * maxStops + 1)` legs plus the user's `detourSlackHrs` slider — a fixed
4 h baseline like the legacy formula collapses on continental graphs.
- `max_distance` is smaller than the shortest feasible tour distance.
- `costs.per_hour` is set (added in upstream VROOM v1.13). Fold into
`costs.per_km` via assumed speed (HGV ≈ 60 km/h).
- Multi-dim `capacity` does not match `amount` length on a shipment.
- `vehicle.profile_options.avoid_polygons` is set but the routing gateway
does not yet forward it to the ORS matrix call.
39 changes: 38 additions & 1 deletion .cortex/skills/build-routing-solution/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,33 @@ Deploys the OpenRouteService route optimization application using Snowpark Conta
| IMAGE_REPO | `ORS_REPOSITORY` | Image repository for SPCS containers |
| COMPUTE_POOL | `ORS_COMPUTE_POOL` | Compute pool for ORS services |

## Sizing

Pre-check before provisioning a new region. The preflight procedure `OPENROUTESERVICE_APP.CORE.VALIDATE_REGION_PREFLIGHT(...)` (added in #52) returns a JSON verdict including the recommended compute size and instance family for any (bounding-box, profiles, compute_size) combination. Call it from the control-app's Region Builder before kicking off `PROVISION_REGION_WRAPPER`, or directly from SQL:

```sql
CALL OPENROUTESERVICE_APP.CORE.VALIDATE_REGION_PREFLIGHT(
36.0, 42.0, -125.0, -114.0, -- California-sized bbox
'driving-car,driving-hgv',
'L'
);
-- Returns {"ok":true, "estimated_pbf_gib":2.83, "estimated_graph_gib":8.49,
-- "recommended_compute_size":"XXL", "recommended_instance_family":"HIGHMEM_X64_L",
-- "warnings":["Estimated graph size 8.49 GiB exceeds the headroom of compute size L. ..."]}
```

Reference table (typical Geofabrik extracts; one driving profile):

| Region scale | bbox area (km²) | PBF (GiB) | Graph (GiB) | Compute size | Instance family | JVM `-Xmx` | `graphs_data_access` |
|---|---|---|---|---|---|---|---|
| Single city (Berlin, SF, Munich) | < 2 500 | < 0.1 | < 0.5 | `S` | HIGHMEM_X64_S | 20 GiB | `RAM_STORE` |
| State / small country (California, Bavaria) | 2 500 – 50 000 | 0.5 – 3 | 1 – 6 | `L` | HIGHMEM_X64_M | 700 GiB | `RAM_STORE` |
| Country / region (USA, EU) | > 50 000 | 3 – 15 | 6 – 20+ | `XXL` | HIGHMEM_X64_L | 1100 GiB | `MMAP` |

Each additional enabled profile multiplies the graph size by ~1.5×. Disable unused profiles in the active `ors-config.yml` (or pick a narrower preset from `.cortex/skills/routing-customization/references/ors-config-presets/`) to keep build time bounded.

**MMAP guidance for continental extracts.** Set `ors.engine.graphs_data_access: MMAP` in `ors-config.yml` whenever the estimated graph exceeds ~5 GiB. This trades a small per-query latency increase for the ability to load graphs that do not fit in RAM, and is mandatory for any extract that hits the 3-day silent hang documented at <https://ask.openrouteservice.org/t/ors-stuck-when-creating-graph-with-european-borders-enabled/>. The shipped [`continental.yml`](../routing-customization/references/ors-config-presets/continental.yml) preset enables this automatically.

## Workflow

> **Fresh install assumed.** This workflow targets a clean Snowflake account with no pre-existing ORS objects. All DDL uses `CREATE ... IF NOT EXISTS` or `CREATE OR REPLACE` with complete schemas from the start. All columns (JOB_ID, GEOGRAPHY, etc.) are defined in the initial CREATE TABLE statements -- no ALTER TABLE migration steps are needed.
Expand Down Expand Up @@ -252,9 +279,19 @@ Follow the full build instructions in `references/build-images.md`. Summary:
MODULES_DIR=".cortex/skills/build-routing-solution/openrouteservice_app/app/modules"

for m in 01_core_infra.sql 02_routing_functions.sql 03_region_management.sql \
04_service_lifecycle.sql 05_matrix_pipeline.sql 06_matrix_ops.sql; do
04_service_lifecycle.sql 05_matrix_pipeline.sql 06_matrix_ops.sql \
07_studio_jobs.sql 08_observability.sql 15_route_optimization_seed.sql; do
bash "$RUN_SQL" <connection> "$MODULES_DIR/$m" || exit 1
done

# Resume the observability ingest + retention tasks created (suspended) by
# module 08. Without this the Observability page in the control app stays
# empty even though the schema exists. Safe to run repeatedly — ALTER TASK
# RESUME is idempotent.
snow sql -c <connection> -q "
ALTER TASK OPENROUTESERVICE_APP.OBSERVABILITY.ORS_METRICS_INGEST_TASK RESUME;
ALTER TASK OPENROUTESERVICE_APP.OBSERVABILITY.ORS_REQUEST_LOG_PURGE_TASK RESUME;
"
```

> **Recovery if 01_core_infra.sql fails partway:** Fix the underlying issue (e.g., grant missing privileges), then re-run the full file. All DDL uses `IF NOT EXISTS` or `CREATE OR REPLACE`, making re-runs safe and idempotent. Alternatively, create only the missing service(s) individually using the corresponding `CREATE SERVICE` statement from the SQL file.
Expand Down
Loading