feat: volume-commitment based routing - #411
Conversation
There was a problem hiding this comment.
🟡 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_contractas a routing-rule payload stored under a newalgorithm_for: volume_commitmentslot, 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
rowsuseMemo will need to re-run whencycleEndMs/secondsLeftchange (they affectstatusoncemissedis 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.
5697081 to
b982de2
Compare
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.
b982de2 to
62f84cf
Compare
…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.
… feat/volume-commitment-routing
What
Demo contract templates, ClickHouse commitment analytics, and the pacing UI for volume-commitment routing.
Endpoints are consolidated
/volume-commitmentand its separate/seriesand/auditpolls 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_planpublishes a plan;compute_planworks 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/projectionneeds, since the merchant it exists for has the feature off.Measurement gains the contract's
amount_scaleand 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_planpropagates 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_cyclesis reintroducedThis restores a gate main removed, deliberately. A
test_minutescontract 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.tomlships 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 --lib— 449 passed, 0 failed (main is at 425; the difference is this branch's controller tests for feasibility, recent-rate and drop confirmation). The suite loadsconfig/development.tomlthrough the real config type and runs every shipped sample past the write-path validator.tsc --noEmitclean,npm run buildgreen, and every endpoint the dashboard calls resolves to a route.Not run: the Playwright e2e suite.