Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.

FieldTypeSource
target_saledecimalSet by user (revenue goal in currency)
target_sale_periodenumdaily / weekly / monthly (AiRecommendation::TARGET_SALE_PERIODS)
recommendation_idstringSet after first RapidCanvas call; used for subsequent refresh requests
pricing_rule.start_date / end_datedateDefines the rule’s active window
pricing_rule.apply_to_all_packagesboolWhether 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:

MetricPurpose
restaurant_idTenant scoping
target_period_typeMapped from target_sale_period: daily→day_end, weekly→week_end, monthly→month_end
start_date / end_dateForecast window (rule’s window, defaulting to 7-day fallback if rule has no end_date)
goal_revenueThe user-set target sale
global_capRestaurant’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 days
  • monthly → 30 days

Approval & Flow

Approval ModeBehavior
Auto-approveRule generates new actions as long as it is active. RecommendationFetchJob runs the orchestrator in the background and creates PriceModifier records directly.
Always require approvalEach 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.

ScenarioMathematical ConditionAI Strategy
Revenue GapTarget Sale > Organic ProjectionPrice decrease (discount) to stimulate demand
Target MetTarget Sale ≈ Organic ProjectionPrice increase to maximize profit margins
High DemandTarget Sale < Organic ProjectionAggressive surge pricing to capitalize on scarcity

Validation: Target Sale can be lower, equal, or higher than Organic Projection.

UI Components

ComponentSpec
Section TitleTarget 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 ProjectionRead-only, formatted with currency symbol and commas. Grey background.
Target SaleEditable numeric, auto-format with commas as user types
Target DurationDisabled, hardcoded to Next 30 Days

Edge Cases

CaseBehavior
Missing Organic Data (new restaurant)RapidCanvas uses platform default elasticity; UI shows Calculating... or N/A
Empty Target SaleBlock save, outline input red: “Please enter a target amount.”
Mid-period scenario shiftDaily 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.

FieldSpec
LabelApply dynamic pricing for the next:
Options1 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.

ComponentSpec
PlacementAdmin General Settings
LabelNotification Sensitivity
Helper Text“Only notify me or require approval if the AI’s new price recommendation differs from my current price by:”

Options

OptionBehavior
System Default (Recommended)Ignores changes < 1%
Custom PercentageUser-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.

FieldSpec
UI PlacementWarning message near AI recommendation output
UX Copy⚠️ Note: Predictions are based on your current package

Demand Ceiling (Unachievable Targets)

Block mathematically impossible targets.

FieldSpec
Backend LogicRapidCanvas calculates “Demand Ceiling” (max theoretical revenue via max price optimization)
Validation UIIf 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.

ComponentSpec
Historical Data UIDisplay 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 ScoreDynamic 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.

FieldSpec
Global Pricing LimitsAI must ingest Global Price Increase/Decrease Limit (e.g., Max 10%); cannot recommend change that violates it
Database Audit LogVersioned 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

MethodPathPurpose
POST/api/recommendations/initialFirst recommendation for a target window
POST/api/recommendations/refreshSubsequent recommendation given a previous recommendation_id
POST/api/recommendations/:id/approveFeedback that user accepted the recommendation
POST/api/recommendations/:id/rejectFeedback that user rejected the recommendation
GET/api/restaurants/:id/predicted_revenueOrganic 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_decision is a no-op (returns blank) when neither RAPIDCANVAS_RECOMMENDATION_DECISION_PATH nor RAPIDCANVAS_BASE_URL is configured

Open Questions / Future Work

  • Time-in-Advance (Yield Horizon) — backend filter implementation status pending; verify in recommendation/generate_initial_recommendation.rb whether time_in_advance_limit is currently applied to the AI output
  • Variance Threshold — UI exists in PRD but no NotificationSensitivity model in pricing-engine yet; tracked as a separate feature
  • Confidence Score — RapidCanvas response field name and mapping to forecast_confidence not yet finalized in response_adapter.rb