Pricing Rules by AI (Rapidcanvas)
AI-Driven Pricing uses the RapidCanvas external recommendation API to suggest price adjustments based on a restaurant’s revenue target and historical performance metrics.
Architecture
┌─────────────────────┐
│ pricing-engine │ Rails 8 API
│ (this service) │
│ │
│ AiRecommendation │ ─── stores target_sale + target_sale_period
│ Recommendation:: │ ─── orchestrates fetch + adapter + modifier creation
│ FetchForRule │
│ RapidCanvas::Client│ ─── HTTP client (initial / refresh / predicted_revenue)
│ RapidCanvas::* │ ─── typed errors (timeout, network, upstream, etc.)
└──────────┬──────────┘
│ HTTPS (x-api-key)
▼
┌─────────────────────┐
│ RapidCanvas API │
│ /api/recommendations/initial
│ /api/recommendations/refresh
│ /api/restaurants/:id/predicted_revenue
└─────────────────────┘
Implementation references:
- Model:
pricing-engine/app/models/ai_recommendation.rb - HTTP client:
pricing-engine/app/services/rapid_canvas/client.rb - Orchestrator:
pricing-engine/app/services/recommendation/fetch_for_rule.rb - Response adapter:
pricing-engine/app/services/recommendation/rapid_canvas/response_adapter.rb - Global cap builder:
pricing-engine/app/services/recommendation/rapid_canvas/global_cap_builder.rb
Rule Configuration
A PricingRule with rule_type = "ai_driven" stores its AI-specific inputs on an associated AiRecommendation record.
| Field | Type | Source |
|---|---|---|
target_sale | decimal | Set by user (revenue goal in currency) |
target_sale_period | enum | daily / weekly / monthly (AiRecommendation::TARGET_SALE_PERIODS) |
recommendation_id | string | Set after first RapidCanvas call; used for subsequent refresh requests |
pricing_rule.start_date / end_date | date | Defines the rule’s active window |
pricing_rule.apply_to_all_packages | bool | Whether the recommendation applies to all packages or just listed ones |
AI Input Metrics (passed to RapidCanvas)
The orchestrator builds a payload that RapidCanvas uses for forecasting:
| Metric | Purpose |
|---|---|
restaurant_id | Tenant scoping |
target_period_type | Mapped from target_sale_period: daily→day_end, weekly→week_end, monthly→month_end |
start_date / end_date | Forecast window (rule’s window, defaulting to 7-day fallback if rule has no end_date) |
goal_revenue | The user-set target sale |
global_cap | Restaurant’s global price limit, enforced by Recommendation::RapidCanvas::GlobalCapBuilder |
Window days by period (Recommendation::FetchForRule::WINDOW_DAYS):
daily→ 0 days (same-day window)weekly→ 7 daysmonthly→ 30 days
Approval & Flow
| Approval Mode | Behavior |
|---|---|
| Auto-approve | Rule generates new actions as long as it is active. RecommendationFetchJob runs the orchestrator in the background and creates PriceModifier records directly. |
| Always require approval | Each generated PriceModifier enters pending status; admin approves/rejects via UI. |
Recommendation Lifecycle
User creates rule (target_sale + period)
│
▼
Job runs Recommendation::FetchForRule
│
├─ If no prior recommendation_id → :initial call
│ → POST /api/recommendations/initial
│
└─ If recommendation_id exists → :refresh call
→ POST /api/recommendations/refresh
│
▼
ResponseAdapter normalizes RapidCanvas response
│
▼
PriceModifier::CreateFromRecommendation creates modifiers
│
▼
Admin approves/rejects → modifiers activate/deactivate
The orchestrator’s request_id format:
recommendation-fetch-job:{operation}:{pricing_rule_id}:{target_date}
AI Strategy Matrix
The RapidCanvas model uses a semilog/exponential prediction. The pricing-engine forwards the user’s target_sale and lets RapidCanvas decide direction.
| Scenario | Mathematical Condition | AI Strategy |
|---|---|---|
| Revenue Gap | Target Sale > Organic Projection | Price decrease (discount) to stimulate demand |
| Target Met | Target Sale ≈ Organic Projection | Price increase to maximize profit margins |
| High Demand | Target Sale < Organic Projection | Aggressive surge pricing to capitalize on scarcity |
Validation: Target Sale can be lower, equal, or higher than Organic Projection.
UI Components
| Component | Spec |
|---|---|
| Section Title | Target Sale |
| Helper Text | “Set your revenue goal. The AI will lower prices to help you reach high targets, or increase prices to maximize your profit if you are already on track.” |
| Organic Projection | Read-only, formatted with currency symbol and commas. Grey background. |
| Target Sale | Editable numeric, auto-format with commas as user types |
| Target Duration | Disabled, hardcoded to Next 30 Days |
Edge Cases
| Case | Behavior |
|---|---|
| Missing Organic Data (new restaurant) | RapidCanvas uses platform default elasticity; UI shows Calculating... or N/A |
| Empty Target Sale | Block save, outline input red: “Please enter a target amount.” |
| Mid-period scenario shift | Daily recalculation; if Scenario 1 becomes 3 mid-month, AI pivots from discount to surge |
Time in Advance (Yield Horizon)
Restrict AI from adjusting prices too far into the future.
| Field | Spec |
|---|---|
| Label | Apply dynamic pricing for the next: |
| Options | 1 Day, 3 Days, 7 Days, 14 Days, 30 Days, Custom [N] Days |
Backend Filter (Action Engine)
IF target_date > (current_date + time_in_advance_limit)
THEN ignore_ai_suggestion AND use_base_price
AI may still predict 30 days out for strategy, but the Action Engine filters before publishing to client.
Variance Threshold (Notification Sensitivity)
Prevent alert fatigue from minor daily AI adjustments.
| Component | Spec |
|---|---|
| Placement | Admin General Settings |
| Label | Notification Sensitivity |
| Helper Text | “Only notify me or require approval if the AI’s new price recommendation differs from my current price by:” |
Options
| Option | Behavior |
|---|---|
| System Default (Recommended) | Ignores changes < 1% |
| Custom Percentage | User-defined threshold; numeric input appended with % |
Backend Logic
- Baseline: Compare against Last Accepted Price (not previous ignored recommendation)
- Default: Trigger alert only if Variance > 1.0%
- Custom: Use user’s percentage instead of 1.0%
AI Recommendation Title Format
Split Heading 1 / Heading 2:
- Title (Bold):
Your Data: Price down X% to gain ~Y% bookings— Template:[Type]: [Price_Action] [Tradeoff] - Subtitle (Muted):
Based on your past booking response to price changes— Template:[Basis]
Package Composition Disclaimer
AI recommends prices for the exact package as it exists today. If price drops 50% (e.g., ฿1,000 to ฿500), booking volume drops, invalidating AI math.
| Field | Spec |
|---|---|
| UI Placement | Warning message near AI recommendation output |
| UX Copy | ⚠️ Note: Predictions are based on your current package |
Demand Ceiling (Unachievable Targets)
Block mathematically impossible targets.
| Field | Spec |
|---|---|
| Backend Logic | RapidCanvas calculates “Demand Ceiling” (max theoretical revenue via max price optimization) |
| Validation UI | If Target Sale > Demand Ceiling, show inline error below Target Sale input |
| Error Copy | “Target exceeds your Demand Ceiling. Seasonal demand is expected to decline this month; aggressive targets may not be achievable through price changes alone.” |
Forecast Transparency
Provide context around “Organic Projection” to build trust.
| Component | Spec |
|---|---|
| Historical Data UI | Display Last Month's Revenue and Last Year's Revenue (Same Month) near Organic Projection |
| Tooltip Trigger | (i) icon next to “Organic Projection” label |
| Tooltip Content | “Calculation: The projection is based on seasonality, recent booking trends, and YoY growth” |
| Confidence Score | Dynamic score from backend: High / Medium / Low |
Audit Log & Global Pricing Limits
Strict paper trail to measure if AI’s original recommendation was effective or if user altered it.
| Field | Spec |
|---|---|
| Global Pricing Limits | AI must ingest Global Price Increase/Decrease Limit (e.g., Max 10%); cannot recommend change that violates it |
| Database Audit Log | Versioned history for every rule interaction |
Logged Data Points
- AI Recommendation: Exact % change suggested by RapidCanvas
- User Action: What user Approved, Rejected, or Edited (e.g., AI 10%, user 5%)
- Timestamps: Recommendation generation, user approval/rejection, scheduled effective date
- Auto Reject: Auto-reject when effective date has passed
RapidCanvas Endpoints
| Method | Path | Purpose |
|---|---|---|
| POST | /api/recommendations/initial | First recommendation for a target window |
| POST | /api/recommendations/refresh | Subsequent recommendation given a previous recommendation_id |
| POST | /api/recommendations/:id/approve | Feedback that user accepted the recommendation |
| POST | /api/recommendations/:id/reject | Feedback that user rejected the recommendation |
| GET | /api/restaurants/:id/predicted_revenue | Organic projection + demand ceiling for a target window |
All endpoints require x-api-key header (from RAPIDCANVAS_API_KEY).
HTTP client behavior
RapidCanvas::Client (pricing-engine/app/services/rapid_canvas/client.rb):
- 3 retry attempts with exponential backoff for 429 / 5xx (
Http::RetryWithBackoff) - 20s request timeout, 10s open timeout
- Typed errors:
TimeoutError,NetworkError,UpstreamError,ConfigurationError,UnexpectedResponseError submit_recommendation_decisionis a no-op (returns blank) when neitherRAPIDCANVAS_RECOMMENDATION_DECISION_PATHnorRAPIDCANVAS_BASE_URLis configured
Open Questions / Future Work
- Time-in-Advance (Yield Horizon) — backend filter implementation status pending; verify in
recommendation/generate_initial_recommendation.rbwhethertime_in_advance_limitis currently applied to the AI output - Variance Threshold — UI exists in PRD but no
NotificationSensitivitymodel in pricing-engine yet; tracked as a separate feature - Confidence Score — RapidCanvas response field name and mapping to
forecast_confidencenot yet finalized inresponse_adapter.rb
Related
- Customer-facing flow (HH Pegasus): Dynamic Pricings 2.0 — Customer Booking Flow
- HH Server integration: Dynamic Pricings 2.0 — HH Server Integration
- pricing-engine customer API: Customer Booking API
- Engineering spec:
PricingEngine/015-PRICING-RECOMMENDATION-API.md - Source models / services:
pricing-engine/app/models/ai_recommendation.rb,pricing-engine/app/services/recommendation/,pricing-engine/app/services/rapid_canvas/ - Sibling PRDs: Capacity & Demand, Date & Time