Skip to content

feat: volume-commitment based routing - #411

Open
jagan-jaya wants to merge 7 commits into
mainfrom
feat/volume-commitment-routing
Open

feat: volume-commitment based routing#411
jagan-jaya wants to merge 7 commits into
mainfrom
feat/volume-commitment-routing

Conversation

@jagan-jaya

@jagan-jaya jagan-jaya commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

What

Demo contract templates, ClickHouse commitment analytics, and the pacing UI for volume-commitment routing.

Endpoints are consolidated

/volume-commitment and its separate /series and /audit polls are removed, replaced by:

  • /dashboard — composes pacing, series and audit into one response. These used to be three hooks against three endpoints polled at two cadences, and because the series hook keyed on its bucket size, two components on one page fetched the same data twice. A page now polls once and every component reads the same snapshot.
  • /projection — what contract routing would do this cycle, from a plan computed on the spot and never stored, so a merchant can see which commitments are still winnable before enabling the feature rather than inferring it from a chart that does not move.
  • /samples — the demo templates, filtering out any this deployment would refuse to store.

Forecast split

build_plan publishes a plan; compute_plan works one out and hands it back without storing it. Reporting a position can no longer leave a plan waiting for the routing path — which is what /projection needs, since the merchant it exists for has the feature off.

Measurement gains the contract's amount_scale and a recent-rate window, so feasibility reads the rate arriving now rather than the cycle average. A merchant whose volume is climbing is no longer written off against a stale average that reports half the rate actually flowing.

Measurement stays fallible

Main made measurement return an error rather than zeros, after this branch forked. That is kept, and compute_plan propagates it: a plan built on an empty measurement would read every PSP as owing its whole goal and steer each at its maximum rate. The pacing card renders the stored plan, so it degrades and reports the gap instead of failing.

allow_test_cycles is reintroduced

This restores a gate main removed, deliberately. A test_minutes contract treats sixty seconds as a billing day — a demo device, meaningless against real billing — so storing one is a deployment-level opt-in. The default is off, so production needs no key to refuse it. Worth a reviewer's attention, since it reverses a choice made on main.

Demo contract templates

config/development.toml ships whole contract documents under [[volume_commitment.samples]]. A merchant picks one, edits the connectors, and activates it; from there it is an ordinary contract. Production ships no entries, and an empty list serves none.

Two scenarios, chosen because they are the verdicts a viewer can watch happen on the chart: a commitment steering saves, and two commitments contending for one traffic budget.

Testing

  • cargo test --lib449 passed, 0 failed (main is at 425; the difference is this branch's controller tests for feasibility, recent-rate and drop confirmation). The suite loads config/development.toml through the real config type and runs every shipped sample past the write-path validator.
  • tsc --noEmit clean, npm run build green, and every endpoint the dashboard calls resolves to a route.

Not run: the Playwright e2e suite.

Copilot AI lite review requested due to automatic review settings September 4, 2026 08:00

Copilot AI left a comment

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.

🟡 Changes recommended

The new UI logic has confirmed correctness issues (cycle position can render NaN/0; run chart never reports “missed” at cycle close and needs correct memo dependencies).

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Implements end-to-end volume-commitment (volume contract) routing: a stored contract document (new Euclid algorithm variant + dedicated activation slot), a pacing/steering engine with its own scheduler server, ClickHouse-backed analytics endpoints, and dashboard + simulator UI to configure, observe, and demo the behavior.

Changes:

  • Added volume_contract as a routing-rule payload stored under a new algorithm_for: volume_commitment slot, including write-time canonicalization + validation.
  • Introduced a volume-commitment controller + scheduler server, Redis plan state, and ClickHouse queries/endpoints for series/audit/impact.
  • Added dashboard/simulator UI + hooks/types and Playwright coverage for creating/activating contracts, steering, and analytics display.
File summaries
File Description
website/src/types/api.ts Adds TS types for volume contracts, pacing views, charts, audit, and decide response steer info.
website/src/hooks/useVolumeCommitment.ts New SWR hooks for dashboard/projection/samples/impact endpoints.
website/src/hooks/useMerchantFeatures.ts Adds volume-contracts as a known merchant feature slug.
website/src/hooks/useHeaderAutoHide.ts New hook to auto-hide the sticky header on scroll down / show on scroll up.
website/src/components/ui/SearchableSelect.tsx Adds triggerClassName to allow input-style trigger rendering.
website/src/components/pages/VolumeContractFeatureNotice.tsx New UI surfaces to explain “contract configured but feature off” and activation summary.
website/src/components/pages/VolumeCommitmentRunChart.tsx New simulator card for commitment pacing + per-PSP steering breakdown and restart control for test cycles.
website/src/components/pages/volumeCommitmentChartBits.tsx Shared chart utils: palettes, bucket sizing, formatting, hatch patterns, conversions.
website/src/components/pages/SRRoutingPage.tsx Adds “Volume Contracts” tab + beta label; wires feature notice into flags UI.
website/src/components/pages/EuclidRulesPage.tsx Filters out volume_contract rules from Euclid rule listing UI.
website/src/components/pages/DecisionSimulatorPage.tsx Adds run chart integration + persists simulation loop state across navigation; captures volume_steer_info.
website/src/components/pages/AnalyticsPage.tsx Adds “Volume commitments” analytics view entry point.
website/src/components/layout/TopBar.tsx Makes TopBar sticky and translatable for auto-hide; accepts hidden + headerRef.
website/src/components/layout/AppShell.tsx Moves TopBar into the scroll container and wires useHeaderAutoHide.
website/src/App.tsx Adds legacy route redirect to the new SR “volume” tab.
website/dist/index.html Updates built asset hash references.
website/dist/decision-engine/index.html Updates built asset hash references for subpath build.
tests/e2e/routing/volume-commitment-steering.spec.ts New e2e: create+activate contract, enable feature, run simulator, verify analytics + audit shows steers.
tests/api/routing/volume-contracts.spec.ts New API tests for canonicalization, validation errors, lifecycle isolation from payment routing.
src/routes/merchant_account_config.rs Adds VolumeContracts feature wiring and maps to the routing gate key.
src/routes.rs Registers new volume_commitment routes module.
src/redis/commands.rs Adds increment_key_by_float (INCRBYFLOAT) helper for shared caps.
src/euclid/utils.rs Validates slot/payload pairing for volume contracts; runs canonical contract validation; gates test cycles by config.
src/euclid/types.rs Adds StaticRoutingAlgorithm::VolumeContract and AlgorithmType::VolumeCommitment.
src/euclid/test.rs Adds round-trip + validation tests for the new algorithm slot/payload invariants and error surfacing.
src/euclid/handlers/routing_rules.rs Adds serde-path error context, write-time canonicalization, slot-scoped mapper fetch, non-evaluable guard, activation stamping, and plan refresh on activate; revalidates/canonicalizes on update.
src/euclid/ast.rs Adds PartialEq to Comparison.
src/euclid.rs Exposes volume_contract module.
src/decider/network_decider/debit_routing.rs Ensures decide response includes volume_steer_info: None for debit path.
src/decider/gatewaydecider/volume_commitment/volume.rs ClickHouse volume measurement source + windowing logic; fixture source for disabled analytics.
src/decider/gatewaydecider/volume_commitment/state.rs Redis-backed plan store with small process-local cache.
src/decider/gatewaydecider/volume_commitment/server.rs Scheduler axum server: /health and /schedule, graceful shutdown.
src/decider/gatewaydecider/volume_commitment/scheduler.rs Tick-based scheduler posting run requests to main server with admin secret.
src/decider/gatewaydecider/volume_commitment/plan.rs Plan model + commitment drop/ranking logic and steering eligibility calculations.
src/decider/gatewaydecider/volume_commitment/mod.rs Feature wiring: deps construction/init, shared constants, exports.
src/decider/gatewaydecider/volume_commitment/math.rs Core pacing arithmetic helpers + unit tests.
src/decider/gatewaydecider/volume_commitment/inputs.rs Commitment input model and input source trait boundaries.
src/decider/gatewaydecider/types.rs Adds volume_steer_info to decider state and new routing approach variant.
src/decider/gatewaydecider/flows.rs Initializes volume_steer_info to None in legacy flow results.
src/decider/gatewaydecider/flow_new.rs Runs volume-commitment nudge after other objectives; records approach and steer info.
src/decider/gatewaydecider/ab_test/preview.rs Rejects volume-contract configs as AB test arms.
src/decider/gatewaydecider/ab_test/interceptor.rs Initializes volume_steer_info to None in AB test intercept path.
src/decider/gatewaydecider/ab_test/evaluator.rs Skips volume-contract configs in AB test evaluator with error log.
src/decider/gatewaydecider/ab_test/config.rs Ensures AB test loads from the payment slot only (algorithm_for-scoped mapper).
src/decider/gatewaydecider.rs Exposes volume_commitment module.
src/config.rs Adds deployment-level volume_commitment config + sample scenario config model and defaults.
src/bin/open_router.rs Builds/installs volume-commitment deps before binding; spawns scheduler server alongside main+metrics.
src/app.rs Adds volume-commitment HTTP endpoints and forecast-run write endpoint.
src/analytics/store.rs Extends analytics store trait with volume-commitment reads (default empty).
src/analytics/models.rs Adds commitment analytics query/window models and response shapes.
src/analytics/flow.rs Adds flow types for volume-contract create/eval and volume-commitment forecast runs.
src/analytics/clickhouse/time.rs Adds origin-based bucketing for cycle-relative series.
src/analytics/clickhouse/mod.rs Wires clickhouse store implementations for volume-commitment analytics + impact.
src/analytics/clickhouse/metrics/mod.rs Registers new commitment metrics modules.
src/analytics/clickhouse/metrics/cost_savings.rs Deduplicates payment amount expr via common helper.
src/analytics/clickhouse/metrics/commitment_series.rs ClickHouse query for cycle-relative delivered volume series (bucketed from cycle start).
src/analytics/clickhouse/metrics/commitment_impact.rs ClickHouse queries for before/after connector totals including ceded volume from srHead.
src/analytics/clickhouse/metrics/commitment_common.rs Shared commitment metric helpers (filters, amount scaling, steered predicate, NaN handling).
src/analytics/clickhouse/metrics/commitment_audit.rs ClickHouse audit reconstruction: forecasts + eliminations + steered decide events.
src/analytics/clickhouse/endpoints/volume_commitment.rs Composed endpoint loader (join series+audit; impact loads four queries concurrently).
src/analytics/clickhouse/endpoints/mod.rs Exposes volume-commitment analytics endpoint module.
src/analytics/clickhouse/common.rs Adds shared payment amount JSON expression + scaling helper.
docs/openapi.json Documents volume_commitment slot and volume_contract payload with example.
config/development.toml Adds dev volume-commitment config, enables scheduler in dev, and provides demo contract samples.
Cargo.toml Adds chrono-tz dependency.
Cargo.lock Locks chrono-tz and its dependencies.
Review details

Suppressed comments (1)

website/src/components/pages/VolumeCommitmentRunChart.tsx:316

  • rows useMemo will need to re-run when cycleEndMs/secondsLeft change (they affect status once missed is considered). Add them to the dependency list so the UI updates at cycle close without waiting for unrelated data changes.
  • Files reviewed: 71/84 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread website/src/components/pages/VolumeCommitmentRunChart.tsx Outdated
Comment thread website/src/components/pages/VolumeContractFeatureNotice.tsx Outdated
@jagan-jaya
jagan-jaya force-pushed the feat/volume-commitment-routing branch from 5697081 to b982de2 Compare September 4, 2026 08:35
Rebased onto main, which has since merged this work's earlier PRs (#367,
#369, #375) and evolved them further. This is that staged change set
applied to main, keeping main's later work rather than reverting it.

Endpoints are consolidated: `/volume-commitment` and its separate
`/series` and `/audit` polls are replaced by `/dashboard`, which composes
pacing, series and audit into one response so a page polls once and every
component reads the same snapshot. `/projection` reports what contract
routing would do this cycle from a plan computed on the spot and never
stored, so a merchant can see which commitments are winnable before
enabling the feature. `/samples` serves the demo templates.

The forecast is split: `build_plan` publishes, `compute_plan` works a
plan out and hands it back without storing it, so reporting a position
cannot leave a plan waiting for the routing path. Measurement gains the
contract's `amount_scale` and a recent-rate window, so feasibility reads
the rate arriving now rather than the cycle average — a merchant whose
volume is climbing is no longer written off against a stale average.

Measurement stays fallible, as main made it. `compute_plan` propagates
the failure instead of building on zeros, which would read every PSP as
owing its whole goal and steer each at its maximum rate; the pacing card,
which renders the stored plan, degrades and reports the gap instead.

Demo contract templates ship as whole `[[volume_commitment.samples]]`
documents. `test_minutes` cycles are gated behind `allow_test_cycles`
again: a contract whose "day" lasts sixty seconds is meaningless against
real billing, so storing one stays a deployment-level opt-in.
@jagan-jaya
jagan-jaya force-pushed the feat/volume-commitment-routing branch from b982de2 to 62f84cf Compare September 4, 2026 09:01
@jagan-jaya jagan-jaya self-assigned this Sep 4, 2026
…lock

Run chart: a commitment short of its goal once the cycle has closed now
reads "missed" rather than staying on "on pace" or "steering". There is
no traffic left to come, so being behind is no longer a position it can
recover from — the same precedence the analytics view already applies to
a past run.

Feature notice: `cyclePosition` validates the parsed cycle start instead
of rendering "day NaN of 31" when it is unparsable, and clamps the day
into the cycle, so one that has not opened yet no longer reads "day 0".

Also applies rustfmt across the files this branch touches.
…ts own line

The promise line was rendering as `stepAfter` over a continuous ramp:
the ramp is sampled only where there is delivered volume to plot beside
it, so stepping it drew a plateau from the last sample to the cycle's end
and then a cliff up to the goal — the promise appearing to arrive all at
once instead of accruing. It is a straight line again.

The per-PSP row held its whole stats line unbroken. The eligibility
reason on the end of it is a sentence, so an unbreakable line overran the
column beside it and printed over that PSP's figures. The counts stay
unbroken; the line as a whole now wraps.
…ty and consistency

- Adjusted terminology in VolumeContractsPage to clarify contract day definitions.
- Removed steering interval from MerchantSettings and related functions.
- Updated cycle summary and document settings to reflect changes in contract day representation.
- Enhanced UI components for better user experience, including template selection and merchant-level settings display.
- Refactored volumeCommitmentChartBits to improve clarity in cycle definitions and bucket calculations.
- Changed public constants in useVolumeCommitment to private scope.
- Removed unused steering_interval_secs from VolumeContractConfig type.
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.

4 participants