Skip to content

Feat/oleksii bielov snow feat#120

Open
sfc-gh-obielov wants to merge 142 commits into
devfrom
feat/oleksii-bielov_snow-feat
Open

Feat/oleksii bielov snow feat#120
sfc-gh-obielov wants to merge 142 commits into
devfrom
feat/oleksii-bielov_snow-feat

Conversation

@sfc-gh-obielov
Copy link
Copy Markdown
Contributor

No description provided.

sfc-gh-obielov and others added 30 commits May 20, 2026 12:30
Data Studio's region dropdown previously only loaded once on mount,
so newly-provisioned regions did not appear until a manual page
refresh. Mirror ServiceManager's 30s poll on /api/ors-readiness, add
a visibility/focus listener for instant refresh on tab return, and
re-fetch regions when an SSE generation job completes or stops.

Bumps ors_control_app image to v1.1.9.
…Snowflake

The /session/v1/logout-from-application endpoint only accepts POST.
The previous GET redirect produced HTTP 405. Replace with an
auto-submitting HTML form so the browser POSTs the redirect_url
and Snowflake clears the session, then bounces back to the SPCS app
login screen.

Bump ors_control_app to v1.1.10.
…ering

RetailCatchment now resolves the ORS service key per region (REGION_NAME first,
then ORS_REGION_KEY) so UsCalifornia (whose REGION_REGISTRY.ORS_REGION_KEY is
the stale 'California') resolves correctly. The dropdown lists every
ORS-provisioned region with that region's actual travel profiles, and the
ISOCHRONES call uses the resolved key.

POI / competitor / density queries now JOIN OPENROUTESERVICE_APP.CORE.REGION_CATALOG
and filter via ST_WITHIN(p.GEOMETRY, rc.BOUNDARY) instead of REGION-equality.
SanFrancisco keeps its fast path against the cached pipeline tables; every
other provisioned region (Germany, UsCalifornia, future ones) reads live from
OVERTURE_MAPS__PLACES / __ADDRESSES polygon-clipped server-side. No SQL
pipeline or Data Studio change required.

AGENTS.md gains a new "Prefer Boundary Polygons over Bounding Boxes" section
under Geospatial Conventions establishing the standard JOIN pattern and
documenting the narrow exceptions where bbox is still acceptable.
…from logout-from-application)

The /session/v1/logout-from-application endpoint returns 404 on this
Snowflake account regardless of HTTP method. Switch /logout to clear
known SPCS ingress auth cookies via Set-Cookie Max-Age=0 and redirect
to /. The SPCS ingress then challenges auth on the next request and
shows the Snowflake login screen.

Also cache-bust the client-side navigation with a timestamp so the
browser does not follow a stale 302 from earlier versions.

Bump ors_control_app to v1.1.12.
…ess logout unreliable)

The /session/v1/logout-from-application endpoint 404s on this account
and clearing candidate SPCS ingress cookies did not end the session
either. Rather than keep iterating on a broken feature, remove the
button and its server route. Users can sign out via Snowsight or by
closing the tab.

Bump ors_control_app to v1.1.14.
…as no coords; guard deck.gl viewState

Fixes a deck.gl Controller assertion (Mfe ctor) that caused the map to
go blank when running OPTIMIZATION (and other functions) in the
Function Tester.

Root cause: a non-finite viewState (NaN/Infinity in
longitude/latitude/zoom) could be fed to <DeckGL controller={true}>
during the first canvas-sizing tick. The OPTIMIZATION path was the
most exposed because parsed paths/stops/depot were not finiteness-checked.

Changes:
- useFitMap: sanitize the initial viewState (drop non-finite fields
  from caller fallback) and export isFiniteVS for downstream use.
- helpers.parseOptimizationResult: filter non-finite [lon,lat] pairs
  in path, stops, and depot.
- helpers.bboxCenter: Number.isFinite-validate each component and
  fall back to [0, 30] (matches DEFAULT_FALLBACK) instead of [0, 0].
- ResultMap: when result-derived coords are empty/invalid, fit the
  map to the selected region's preset boundary (boundaryGeoJson
  preferred, bbox corners as secondary). Defer <DeckGL> mount until
  container dims AND viewState are both finite, eliminating the
  Controller-init race.
- FunctionTester: pass selectedRegion.bbox and boundaryGeoJson to
  ResultMap so the preset fallback has data to fit to.
Ghost trailers were being assigned by global per-vehicle Bernoulli sampling,
which caused large regions (e.g. UsCalifornia) to concentrate all ghosts in
the densest POI cluster (SF Bay), making Asset Velocity look SF-only when
the California preset was active.

buildFleet() now bins fleet members by 0.5 deg lat/lng cells (~55 km) and
samples ghost_count = bin_size * probability per bin with stochastic
rounding, preserving the expected total ghost count while spreading ghosts
across the whole region.

Bumps ors_control_app to v1.1.15.

.... Generated with [Cortex Code](https://docs.snowflake.com/en/user-guide/cortex-code/cortex-code)

Co-Authored-By: Cortex Code <noreply@snowflake.com>
The shared useFitMap hook and MapView component used to re-fit the camera
on every coords-signature change, which produced a jarring snap whenever
data refreshed (e.g. live polling, filter toggles).

Now the camera auto-fits only once per (mount, regionKey) pair: it fits
on the first non-empty data load, refits when the active region changes,
and otherwise leaves the user's pan/zoom alone. A new RecenterButton
overlay (top-right of every map) lets the user re-fit on demand.

All 20 useFitMap consumers across the React app (taxis, food delivery,
dwell, route deviation, retail catchment, route/asset velocity, backload,
matrix viewer, agent playground, intro, function tester) now pass a
regionKey and render the Recenter button.
Three changes ensure the Route Optimization page works for any newly provisioned
region without requiring a manual region switch (which previously left big
regions like UsCalifornia with zero PLACES because the synchronous SPCS auto-seed
timed out for ~1.8M Overture rows):

1. PROVISION_REGION_WRAPPER now calls SEED_ROUTE_OPTIMIZATION_REGION on its
   success path (next to SET_REBUILD_GRAPHS_FLAG and create_region_vroom_service).
   This runs inside the stored-procedure context with no SPCS request timeout, so
   PLACES is guaranteed populated by the time STATUS=DEPLOYED.

2. seed-data.sql now loops over every REGION_ORS_MAP STATUS='DEPLOYED' region and
   seeds each one (idempotent), so a fresh install or re-run guarantees coverage
   across all already-provisioned regions.

3. The two SPCS auto-seed call sites (POST /api/regions/active and
   POST /api/fleet/datasets) are now detached (no await) so a slow seed for a
   large region doesn't block the HTTP response. The existing UI warning already
   tells users to refresh after a moment.

Bumped ors_control_app to v1.1.17. Verified on UsCalifornia: 1,824,125 places
seeded, 42,734 within 5km of SF Civic Center.

.... Generated with [Cortex Code](https://docs.snowflake.com/en/user-guide/cortex-code/cortex-code)

Co-Authored-By: Cortex Code <noreply@snowflake.com>
The vehicle profile dropdown previously hardcoded Car/HGV/Bicycle and defaulted
every new vehicle to driving-car. Regions provisioned with a different profile
mix (e.g. UsCalifornia and Germany are HGV-only) caused the optimize call to
fail with: "Parameter 'profile' has incorrect value of 'unknown'".

Now mirrors the pattern from RetailCatchment / FunctionTester:
- On region change, query ORS_STATUS(region) and parse the profiles object.
- Populate the dropdown from those profile keys (with friendly labels).
- Reconcile existing vehicles: any vehicle whose profile is no longer loaded
  for the active region is auto-switched to the first available profile.
- Default new vehicles ("+ Add" button and initial state) to availableProfiles[0].
- Disable the Optimize button when no profiles are available with a tooltip
  pointing at the region's ORS readiness.

Bumped ors_control_app to v1.1.18.

.... Generated with [Cortex Code](https://docs.snowflake.com/en/user-guide/cortex-code/cortex-code)

Co-Authored-By: Cortex Code <noreply@snowflake.com>
Recenter previously fit camera to hex centers (cellToLatLng), which
under-sized the bbox by up to one edge length and clipped outer hexes,
especially at low resolutions and on extruded+pitched views.

- add coordsFromH3Cells() in shared/mapFit.ts using cellToBoundary so
  the bbox encloses each hex's full footprint (sampled to 2000)
- add shared/useH3FitMap.ts wrapper hook with optional extraCoords for
  mixed point+hex maps
- migrate CongestionMap, CourierHeatmap, fleet-taxis HeatMap to the new
  hook; bump top padding (100/60/60/60) for extruded views to prevent
  vertical clipping of elevation columns
- update MatrixViewer (mixed point+hex) and Intro to use
  coordsFromH3Cells when computing fit bounds
Previously the fit-once gate in useFitMap was keyed only on regionName, so
switching the active matrix (e.g. SF -> California) within the same region
left the camera parked over the previous bbox even though fitCoords had
fully recomputed for the new matrix.

Build a composite fitKey from regionName + activeTable so a matrix switch
counts as a region change and the auto-fit gate resets.

Bumps image to v1.1.22.
loadRandomOrigin updates state in two commits separated by an await: A
sets originHex/originLat/originLon/activeTable while destinations is
still []; B then sets destinations(dests) after fetchReachability.

With the previous fitKey = regionName|activeTable, commit A flipped the
key, useFitMap reset hasFittedRef and fit a single-point bbox at zoom 14
on the new origin, locking hasFittedRef=true. Commit B's full hex
extents then hit the early-return gate, so the camera stayed pinned on
the origin instead of zooming out to fit the full reachability area.

Widen fitKey to also include originHex, mode, destHex, and a
hasDests boolean so the destinations-loaded transition flips the key
again and the hook re-fits with the complete dataset. driveTimeLimit /
slider state is intentionally excluded so adjusting time does not jerk
the camera.

Bumps ors_control_app image to v1.1.23.
…e echo

DevTools probe on v1.1.23 confirmed viewState was pinned to the
hardcoded fallback even when 5 layers and the recenter button were
mounted. The auto-fit effect's setViewState was being silently reverted
by deck.gl 9 echoing the previous viewState back through
onViewStateChange in controlled mode.

- Tag every programmatic setViewState in useFitMap with
  transitionDuration: 600 + FlyToInterpolator so deck.gl honors the
  update as an explicit transition.
- One-shot ref guard (echoGuardRef = 2) suppresses the next two
  onViewStateChange events fired in response to the programmatic
  update, preventing the revert. User pan/zoom events still flow.
- Add coordsRef and dimsRef for closure safety on future imperative
  paths.

Bumps image to v1.1.26 (parallel session bumped past v1.1.23).
… ensureTables

Snowflake's ALTER TABLE ADD COLUMN IF NOT EXISTS raises a spurious
"ambiguous column name" compile error when the column already exists.
This killed startGeneration before any data work began.

Two-layer fix:
1. Wrap backfill ALTERs (EVENT_TS, LOG_TEXT) in EXECUTE IMMEDIATE +
   BEGIN…EXCEPTION so the error never escapes Snowflake.
2. Add catch-block guard for "ambiguous column name" / "already exists"
   patterns → log as INFO no-op and continue.

Deployed as ors_control_app:v1.1.26.

.... Generated with [Cortex Code](https://docs.snowflake.com/en/user-guide/cortex-code/cortex-code)

Co-Authored-By: Cortex Code <noreply@snowflake.com>
…al clarity

Adds a UI toggle and editable lat/lon inputs in the Backload Matching
solver controls. When enabled, every VROOM vehicle.end is set to the
same coordinate so all solved trailer routes converge visually on one
point. Defaults to Copenhagen (12.5655, 55.6759) anchor. KPIs are
unaffected because EMPTY_KM is computed from pickup-to-dropoff
distance, independent of vehicle.end. Bumps ors_control_app to v1.1.27.
…hared hooks

Cherry-picks the SUMMIT branch's richer Route Optimiser (VRP) page and adapts
it to the current branch's refactored architecture (useFitMap, RecenterButton,
coordsFromGeoJSON, availableProfiles from ORS_STATUS, region-aware OPTIMIZATION
and ISOCHRONES calls, _OPTIMIZATION_RAW error probe).

New UX:
- Industry-driven flow (industry first, then search location)
- Per-skill capacity, dynamic skill labels from LOOKUP.STYPE
- Universal depot selector (DEPOT_CTYPE / DEPOT_LABEL from LOOKUP) — works for
  SEN Transport (schools) and any other industry that defines a depot category
- Job templates editor with maxJobs slider
- Fleet recommendation engine (vehicles per skill = jobs / capacity)
- Two-tab result panel: Map / Job Assignments with turn-by-turn directions
- Unassigned jobs banner with Cortex-generated explanation
- POI/Routes/Catchment/Depot layer toggles + dark tooltip with route colour

Schema fallbacks: queries against LOOKUP.SOURCE_TABLE/DEPOT_CTYPE/DEPOT_LABEL
and JOB_TEMPLATE.INDUSTRY are wrapped in try/catch so the component degrades
gracefully on installs that haven't applied the v1.0.185 schema migration.
… + capacity

Adds the columns the new VRP UI reads:
  LOOKUP.SOURCE_TABLE   STRING  — optional override table (e.g. SEN_STUDENTS)
  LOOKUP.DEPOT_CTYPE    ARRAY   — POI categories that can serve as depots
  LOOKUP.DEPOT_LABEL    STRING  — human-readable depot label (e.g. "School Destinations")
  JOB_TEMPLATE.INDUSTRY STRING  — links job templates to LOOKUP rows

CREATE TABLE statements add the new columns inline; ALTER TABLE ADD COLUMN
IF NOT EXISTS statements backfill them on installs predating v1.0.185.

The SEED_ROUTE_OPTIMIZATION_REGION procedure is updated to:
- Carry the new LOOKUP columns through the cross-region copy path
- Emit the canonical 5-industry catalog (healthcare/Food/Cosmetics/Beverages/
  SEN Transport) on first seed, with depot configs from SUMMIT
- Emit 49 SLOT_START/SLOT_END rows in seconds-from-midnight (matches the new
  UI's secsToHHMM renderer)
- Provision a SEN_STUDENTS override table with synthetic student-pickup
  addresses derived from real PLACES rows within 15km of any school
Previous default (Copenhagen 12.5655, 55.6759) lay outside non-Nordic
routing graphs (Germany etc.), causing OPTIMIZATION to return zero
assignments because the vehicle.end fell outside ORS coverage.

Now sharedDestLon/Lat default to null and are auto-populated from
trailers[0].HOME_LON/HOME_LAT whenever the toggle is enabled and the
user has not edited the inputs. The fill re-runs when trailers reload
(on region switch), so the default always sits inside the active
region. Editing either input pins the user's value.

Bumps ors_control_app to v1.1.28.
Spreads home POI assignment and trip destinations evenly across the
active region polygon for ANY of the 5,194 regions in REGION_CATALOG,
instead of inheriting Overture Maps' POI density (which clustered HGV
trips in NRW for Germany, Bay Area for California, Paris for France).

- engine/spatial.ts: new region-agnostic helpers (binPoisByLatLng,
  binDegForArea, bboxAreaKm2, spreadStats). bin_deg auto-derived from
  REGION_CATALOG.BOUNDARY_AREA_KM2 (5km bins for cities, 1deg for
  continents), zero per-region branches.
- engine/fleet.ts: buildFleetWithDiagnostics rounds-robin home POI
  assignment over BINS, not POIs, so every populated bin gets ~equal
  vehicle share. Falls back to original behaviour when < min_bins
  populated (small regions).
- engine/routing.ts: pickDestination bins the candidate pool by
  spatial cell, picks bin uniformly then POI within. Identical to
  before when only one bin populated.
- profiles.ts: defaultDistanceDistributionForArea fills area-aware
  defaults (more long trips for larger regions); explicit template
  values preserved.
- routes.ts: resolveRegionAreaKm2 reads BOUNDARY_AREA_KM2; injects
  region_area_km2 + spatial_spread defaults into config at /generate.
- jobs.ts: emits per-bin diagnostics (bin_deg, populated_bins,
  top_bin_share, vehicles per bin p50/p95) so users can verify spread.
- ors_control_app_service.yaml: bump to v1.1.28.

Default ON for all regions; user can override bin_deg or disable via
config.spatial_spread.
…al offset

When 'Force shared destination' is on, all trailers head toward the
same end point. Previously the loaded-leg GeoJsonLayer painted each
route on top of the previous one with alpha 230, so on the shared
corridor only the last-drawn colour remained visible.

Replace the per-assignment GeoJsonLayer with a single PathLayer that
uses PathStyleExtension({ offset: true }) plus getOffset =
(idx - (N - 1) / 2) * 1.5 px. Routes spread laterally into a parallel
ribbon centred on the real road, so all N assignments are visible
along the shared corridor. Lateral offset is gated to forceSharedDest
to avoid bending naturally diverging routes when the toggle is off.

Bumps ors_control_app to v1.1.29.
sfc-gh-obielov and others added 21 commits May 27, 2026 13:02
…audit

Findings from the post-implementation audit of PR #120 issues:

- query_tag missing on 4 SQL modules (01_core_infra, 03_region_management,
  04_service_lifecycle, 06_matrix_ops) - per-AGENTS.md requirement.
- SQL injection in ESTIMATE_MATRIX_COST (#39): P_REGION user input was
  concatenated directly into EXECUTE IMMEDIATE under EXECUTE AS OWNER.
  Sanitised via REPLACE single-quote escape.
- ORS_REQUEST_LOG (#56) had no retention; would grow unbounded as
  V_ORS_METRICS_SUMMARY scans -24h with APPROX_PERCENTILE. Added
  DATA_RETENTION_TIME_IN_DAYS=1 plus a daily ORS_REQUEST_LOG_PURGE_TASK
  (cron 0 4 UTC, suspended) deleting rows older than 30 days.
- Gateway retry jitter (#50) only produced 3 distinct values
  (req_id[-2:].count('a') / 2). Replaced with random.uniform(0.75, 1.25)
  so concurrent callers don't synchronize retries.
- _retry_matrix_chunked silently dropped failed chunks at the smallest
  chunk_size, returning a stitched matrix with missing destination
  columns and no signal to the caller. Added depth-2 recursion guard
  plus _partial:true marker + failed_destinations list.
- Documented the single-profile canary limitation in waitForOrsGraphReady
  (#53) so future maintainers don't re-discover the trade-off.

Refs PR #120; closes audit follow-ups for #39 #50 #53 #56.
… to v1.1.3

Three bugs discovered during e2e smoke testing of PR #120:

1. Wrong container name in SYSTEM$GET_SERVICE_LOGS call. The procedure
   was passing container='routing-gateway' but the actual container in
   routing-gateway-service.yaml is 'reverse-proxy'. SYSTEM$GET_SERVICE_LOGS
   silently returned empty -> procedure short-circuited with reason='empty_logs'
   -> ORS_REQUEST_LOG never received any rows.

2. tail_lines = 5000 exceeds the SYSTEM$GET_SERVICE_LOGS hard cap of 1000.
   Lowered to 1000. The 1-minute task schedule plus the 5-minute overlap
   window in the default cutoff is enough headroom for typical traffic.

3. Cutoff filter compared TIMESTAMP_LTZ against SYSDATE(). SYSDATE() is
   TIMESTAMP_NTZ in account TZ (PT for this account), so every parsed
   metric ts (UTC LTZ) appeared 7+ hours before the cutoff and was
   filtered out. Switched to CURRENT_TIMESTAMP() (LTZ).

After all 3 fixes: ingest parses metric lines, INSERTs them into
ORS_REQUEST_LOG, and V_ORS_METRICS_SUMMARY returns valid p50/p95/error_rate.
Both ORS_METRICS_INGEST_TASK and ORS_REQUEST_LOG_PURGE_TASK resumed.

Also bumped routing_reverse_proxy v1.1.2 -> v1.1.3 (gateway image with
random.uniform jitter + _retry_matrix_chunked depth guard from the
audit-pr-120 commit) and updated routing-gateway-service.yaml + image-versions.env
Demos exercise the gateway with larger matrices than the default
ORS_GUARDRAIL_MATRIX_MAX_LOCATIONS=200 allows:

- BackloadMatching: BM_MAX_MATRIX_LOCATIONS = 500 unique locations
- Studio routability: BATCH_SIZE = 1 source + 1000 destinations per
  MATRIX_TABULAR call
- AssetVelocity: 50 + 50 = 100 (already fits)

Engine-side caps in ors-config.yml are already permissive:
matrix.maximum_routes = 2,000,000 (sqrt = 1414), so the gateway env
var is the only bottleneck. Set it to 1500 -- covers both demos with
headroom under the engine ceiling, and _retry_matrix_chunked still
recovers via 50 -> 10 fallback.

Also bumped:
- ORS_GUARDRAIL_ISOCHRONES_MAX_LOCATIONS 2 -> 5 (what-if comparisons)
- Other guardrails kept at engine-aligned defaults.

Verified end-to-end:
- 250-loc matrix: returns 250 duration rows (was rejected before).
- 1000-loc matrix: returns 1000 duration rows.
- 2000-loc matrix: still rejected with structured request_too_large
  error citing the new cap of 1500.

No image rebuild; just spec env vars + ALTER SERVICE cycle.
Bug: activateDataset() in server/studio/jobs.ts toggled IS_ACTIVE on the
target DIM_DATASETS row without checking whether that DATASET_ID had any
matching rows in the base tables. Foot-gun: when a placeholder row
(legacy '-seed' recovery rows, or a row whose base data was deleted
out-of-band) gets activated, every V_*_CURRENT view silently returns
zero and the fleet/freight pages are empty with no diagnostic.

Reproduction: delete all non-default datasets for (region, vehicle_type),
switch to the remaining (default/seed) one, watch every fleet page go
empty.

Fix: probe COUNT(*) FROM DIM_FLEET WHERE JOB_ID=<datasetId> before the
UPDATE. If zero, throw with an actionable message ("re-run Data Studio
or pick a different dataset"). The /api/studio/datasets/:id/activate
route already wraps in try/catch and returns 400, so the user sees
the message as a UI toast.

Bumped ors_control_app v1.1.54 -> v1.1.55. Spec + image-versions.env
updated; image already pushed and live in SPCS.
…ult idle threshold

The Asset Velocity matrix call passed a dollar-quoted JSON string literal
(VARCHAR) to OPENROUTESERVICE_APP.CORE.MATRIX, which only has ARRAY and
VARIANT overloads. The planner reported 'Invalid argument types for
function MATRIX: (VARCHAR(16), VARCHAR(3636), VARCHAR(12))'. Wrapping
with PARSE_JSON(...) selects the (VARCHAR, VARIANT, VARCHAR) overload.

Also lower the default idle threshold from 4h to 1h so the panel surfaces
idle vehicles on fresh installs (Studio runs only generate ~24h of dwell
data, so 4h+ thresholds yield 0 rows). Helper text updated to reflect the
new default and recommend raising to 72h+ for ghost-trailer-only views.

Bumps ors_control_app image v1.1.55 -> v1.1.56.
…windows; skip break for non-HGV

The 'Profile may not be available' message was a guess that fired whenever
OPTIMIZATION's TVF returned 0 rows. The TVF flattens resp:routes, so VROOM
returning routes:[] yields 0 rows even when the call succeeded. Re-query
_OPTIMIZATION_RAW on the empty path and report summary.unassigned plus the
first few unassigned descriptions instead.

vroom-map
The 'Profile may not be available' message was a guess that fired whenever
OPTIMIZATION's TVF returned 0 rows. The TVF flattens resp:routes, so VROOM
returning routes:[] yields 0 rows even when the call succeeded. Re-query
_OPTIMIZATION_RAW on the empty path and report summary.unassigned plus the
first few unassigned descriptions instead.

vroom-map
The 'Profile may not be available' message was a guess that fired whenever
OPTIMIZATION's TVF returned 0 rows. The TVF flattens resp:routes, so VROOM
returningnd forcing a break inside the
  shift window makes VROOM drop the vehicle.
- capacity[0] raised from 1 to terminals.length so a vehicle isn't capped
  at one stop.

Bumps ors_control_app image v1.1.56 -> v1.1.57.
…ASS_PROFILE

On a fresh SanFrancisco/ebike install, "Solve Backloads" returned
"VROOM placed 0 shipments out of 90. Top reasons: 90x unknown" and
the log showed profile=driving-car for an ebike preset. Four bugs:

  1. VW_TRAILERS used DIM_FLEET.BATTERY_RANGE_KM (EV range, not payload)
     so ebike vehicles got tiny capacities that no shipment fits.
  2. VW_INTERNAL_VOLUMES anchored pickup windows at TRIP_START (past)
     while vehicle shift starts at min(ETA) (past); both stale.
  3. BackloadMatching.tsx picked provProfile.split(',')[0] - the first
     ORS profile provisioned for the region - regardless of the active
     vehicle_type, so an ebike preset solved on the driving-car graph.
  4. Unassigned reasons read u.reason; VROOM v1.14 returns description,
     so every rejection bucketed as "unknown".

Fix: introduce OPENROUTESERVICE_APP.CORE.VEHICLE_CLASS_PROFILE as the
single source of truth for per-class capacity, costs, ORS profile, and
UI label. Eight seeded classes (bicycle, ebike, foot, motorcycle, car,
van, hgv, truck). Init.ts and bootstrap.sql now drive VW_TRAILERS
payload, VW_INTERNAL_VOLUMES + VW_EXTERNAL_OFFERS weights, and pickup
window anchor from the profile. React binds ORS profile to the
vehicle_type's canonical profile and refuses to solve when the region
isn't provisioned with it. Vehicle shift floored to "now". Pre-flight
catches median-shipment-exceeds-capacity and no-window-overlap before
calling VROOM. Unassigned parser reads description ?? reason ?? type.
Class-aware post-mortem prepends a wrong-class hint when 100% fail.
Image v1.1.59 deployed.

.... Generated with [Cortex Code](https://docs.snowflake.com/en/user-guide/cortex-code/cortex-code)

Co-Authored-By: Cortex Code <noreply@snowflake.com>
…rks for any class

The Optimize Repositioning button was hard-coded for HGV semantics:
RESTAURANT terminals demanded a REEFER skill (id=1) but only trucking
trailers (with VEHICLE_SUBTYPE) ever offered REEFER. For ebike/SF the
class has no subtype, so every restaurant job was unassignable and VROOM
returned routes:[]. The new toast surfaced the symptom but not the root
cause.

Asset Velocity now loads OPENROUTESERVICE_APP.CORE.VEHICLE_CLASS_PROFILE
for the active CONFIG.VEHICLE_TYPE (the same loader Backload Matching
already uses) and drives the VROOM challenge from that single row:

- profile     <- klass.ORS_PROFILE (cycling-electric for ebike, etc.)
- ENFORCE_BREAK gates the EU-561 mandatory 45-min break (HGV only)
- ENFORCE_BREAK gates skill matching (REEFER/FLAT/TANKER/HAZMAT)
- capacity    <- [maxStops, PAYLOAD_KG_TYP, PAYLOAD_KG_MAX]
- service     <- 300s for couriers/cars; 600-1800s for trucking by terminal
- exclusion   <- skill mismatch only when the active class is trucking

If VEHICLE_CLASS_PROFILE has no row for the active vehicle_type the page
refuses to optimize and surfaces a friendly error instead of silently
returning 0 routes.

Bumps ors_control_app image to v1.1.60.
…installs

The Observability page renders SQL 422 ("schema does not exist") on accounts
where module 08_observability.sql was never applied. Two-layer fix:

1. SKILL.md deploy loop now includes 07_studio_jobs.sql, 08_observability.sql,
   and 15_route_optimization_seed.sql (previously stopped at 06), and resumes
   the ORS_METRICS_INGEST_TASK + ORS_REQUEST_LOG_PURGE_TASK after deploy. The
   automated scripts/deploy.sh already iterated *.sql so this aligns the
   manual SKILL.md path with it.

2. server/lib/init.ts gains ensureObservabilityObjects() which idempotently
   creates the OBSERVABILITY schema, ORS_REQUEST_LOG table, and
   V_ORS_METRICS_SUMMARY view on every container boot. Mirrors module 08 so
   the page renders even on accounts that skipped that module — empty rather
   than 422. Procedure + tasks remain owned by module 08 (operator runs that
   to actually populate data).
Reduces SQL API round trips ~4x for the per-day telemetry flush in
insertTelemetryBatch. A typical 65k-point day previously needed 130
sequential INSERTs (~5 min); 2000-row UNION ALL stays well under
Snowflake parser limits and cuts that to ~33 INSERTs.
… flush

generateTelemetry now dispatches up to config.parallelism (default 8)
vehicle-day workers concurrently within each simulated day instead of
running the fleet sequentially. Each worker's ORS route calls,
interpolation, and dwell emission run independently; the parent generator
drains results race-style via Promise.race over an in-flight map.

Telemetry is also yielded incrementally: when the day buffer reaches
2000 points it is flushed mid-day, so INSERT batches overlap with the
next vehicles' ORS calls and the SSE progress stream stays alive between
days (no more multi-minute apparent pause).

Recovery and hard-stop logic are now applied in the parent loop after
each worker resolves, with a single-flight tryRecover() helper so
parallel workers don't trigger redundant ALTER SERVICE RESUMEs.
Per-vehicle RNG seeds are derived up-front from the global rng so any
single vehicle's emitted sequence stays self-consistent regardless of
worker completion order.

Adds optional `parallelism` field to GenerationConfig (default 8, set to
1 for fully sequential reproducible runs).

Expected speedup: ~5-8x on the ORS phase (was the dominant cost), so a
50-vehicle 7-day run drops from ~2h to ~25-30 min. Backend is sized for
this: ORS_SERVICE_<REGION> already runs 4 instances and
ROUTING_GATEWAY_SERVICE 8.
The control-app container is now the orchestrator for parallel Studio
generation (up to 8 concurrent vehicle-day workers) and accumulates
larger 2k-point telemetry buffers in memory before each flush. The
previous 0.5/1 CPU + 0.5/1 GiB ceiling left no headroom for the parallel
I/O and was unrelated to ORS itself (which runs in ORS_SERVICE_<REGION>).
…xtend-dim-fleet-hgv view-drop fix

1. AGENTS.md: document the DIM_DATASETS bootstrap invariant (friction-log F4
   fix) — init.ts must call ensureUnifiedTables() before V_*_CURRENT views,
   and ALTER TABLE on DIM_FLEET must DROP the dependent view first.
2. extend-dim-fleet-hgv.sql: add DROP VIEW IF EXISTS V_DIM_FLEET_CURRENT
   before ADD COLUMN statements to prevent column-count mismatch on next boot.
3. Friction log from today's full deploy captured.

.... Generated with [Cortex Code](https://docs.snowflake.com/en/user-guide/cortex-code/cortex-code)

Co-Authored-By: Cortex Code <noreply@snowflake.com>
…road route on map

Switch offers read to VW_OFFER_ENRICHED_V2 for cached ROUTE_GEOMETRY, add
useSelectedOfferRoute with live /api/fx/offer-route fallback, and render
pickup/dropoff markers plus PathLayer on the map. Bump ors_control_app to v1.1.61.

Co-authored-by: Cursor <cursoragent@cursor.com>
…changes

Expose activeDatasetId from /api/fleet-config and include it in the page
dataKey so Freight Exchange and sibling pages refetch after a Studio rerun.

Co-authored-by: Cursor <cursoragent@cursor.com>
…03 on read-only /api/query)

The page's regionName useEffect was issuing two UPDATE statements through
sfQuery, which posts to /api/query. That endpoint is intentionally read-only
(SELECT/SHOW/DESCRIBE/DESC/CALL/WITH) and rejected the UPDATEs with HTTP 403
'Only read-only queries allowed'. The first failure was swallowed silently;
the second logged a visible warning whenever the user switched region.

CONFIG.REGION + CONFIG.VEHICLE_TYPE (on both BACKLOAD_MATCHING.CONFIG and
ROUTE_OPTIMIZATION.CONFIG) are already kept in sync atomically by
POST /api/datasets/activate, which runs server-side via runSql and updates
all 6 demo CONFIG tables in one round-trip. The page only needs to refetch
after the region changes.

This mirrors the same v1.1 fix already applied in AssetVelocity.tsx
(see comment at AssetVelocity.tsx:71-74).
Expose hybrid orsProfile on /api/fleet-config, add useActivePreset and
PresetRoutingControls, and wire all ORS-calling demos to preset region/profile.

Co-authored-by: Cursor <cursoragent@cursor.com>
…t profile, gateway multi-isochrone

- Fix DIRECTIONS/ISOCHRONES/MATRIX/OPTIMIZATION SQL in freight_exchange.ts (FROM TABLE, arg order)
- Resolve routing profile from MARKETPLACE.CONFIG JOIN DIM_DATASETS (active preset)
- Add gateway POST /isochrones + SQL multi-point ISOCHRONES overload (routing_reverse_proxy v1.1.4)
- Drop client vehicleType from offer-route; bump ors_control_app to v1.1.63

Co-authored-by: Cursor <cursoragent@cursor.com>
Preset-sync effects no longer depend on local selectedRegion/selectedProfile,
so changing region or transport type in Directions & Isochrones stays put
until the header dataset preset actually changes.

Co-authored-by: Cursor <cursoragent@cursor.com>
Tag DATASET_EMPTY and BOOT_INCOMPLETE in activateDataset, map to 409/503
in /api/datasets/activate, gate recovered DIM_DATASETS rows on DIM_FLEET,
expose fleetRowCount in GET /api/datasets, and fix DatasetPicker to check
res.ok with inline errors and unavailable preset pills (v1.1.65).

Co-authored-by: Cursor <cursoragent@cursor.com>
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