Skip to content

AI Recommendations

Flow ID: CF-34 | Module(s): ai, eshop, job, plus | Complexity: High Last Updated: 2026-04-06

Business Overview

Advisable AI is an external ML-based recommendation engine that powers personalized product suggestions across the storefront. The platform syncs its product catalog to the Advisable AI service, tracks customer behavior (views, cart actions, bookmarks, ratings, purchases), and fetches personalized recommendations that are rendered in configurable page positions.

What customers experience:

  • Personalized product carousels on the home page, product page, cart, minicart, category page, and checkout
  • "Frequently bought together" suggestions on product pages
  • History-based recommendations that rotate daily
  • Post-add-to-cart recommendation popups with configurable algorithm priority
  • Recommendation attribution: when a customer clicks a recommended product and eventually purchases it, the conversion is tracked back to the specific recommendation

Two distinct AI subsystems exist:

  1. Advisable AI Recommendations -- external ML engine for product recommendations (this flow)
  2. AI Content Generation -- OpenAI-powered text generation for product descriptions and admin content (separate subsystem sharing the ai/ module namespace)

Key business behaviors:

  • Feature gated by ADVISABLE_AI.ENABLED registry key
  • Can be force-disabled per-environment via ADVISABLE_AI_FORCE_DISABLE env variable
  • Bots/crawlers are excluded from all AI interactions ($this->agent->is_robot())
  • Circuit breaker protection prevents cascading failures when the external API is down
  • All storefront-to-AI calls are deferred (non-blocking) via DeferredTaskRunner to avoid impacting page load time
  • Recommendation tracking flows through the full purchase funnel: track_type and track_id are persisted to shop_order_basket rows for revenue attribution
  • History-based recommendations cache the recommendation ID in the session, rotating once per day
  • Pagination through recommendation results uses a skip counter stored in session, capped by ADVISABLE_AI.CUSTOMER_MAX_PRODUCTS (default 50)

Architecture

The AI recommendation system spans three architectural layers: the external ecommercen/ai-connector Composer package (HTTP client), the legacy HMVC ai module (business logic), and the modern domain/REST layer (admin CRUD for positions and content generation logs).

Component Map

ComponentPathPurpose
External Packagevendor/ecommercen/ai-connector/HTTP client, DTOs, query builders for the Advisable AI REST API
Core AI Connectorecommercen/ai/libraries/CoreAIConnector.phpBase class: config loading, Monolog logger, timestamp formatting
AI Connectorecommercen/ai/libraries/AdvAIConnector.phpFull HTTP client wrapping the external package for all event and recommendation calls
Admin AI Connectorecommercen/ai/libraries/AdvAdminAIConnector.phpAdmin operations: create/delete clients and apps with bearer token auth
Business Wrapperecommercen/ai/libraries/AdvAdvisableAI.phpHigh-level facade with circuit breaker, customer user management, product ID resolution
Recommendation Parserecommercen/ai/libraries/AdvAdvisableAIProductRecommendationParser.phpConverts ItemsRecommendation responses to product card objects with track type/ID
View Modelecommercen/ai/libraries/AdvAIRecommendationViewModel.phpDisplay logic: min-item checks, stock filtering, live data merge
Page Positionsecommercen/ai/libraries/AdvAIRecommendationPages.phpConstants: HOME, CART, MINICART, PRODUCT, CATEGORY, CHECKOUT
Recommendation Typesecommercen/ai/libraries/AdvAIRecommendationTypes.php9 algorithm types (similar items, FBT, history-based variants, most popular)
Response Transformersecommercen/ai/libraries/AiRecommendation*Transformer.phpTrait + interface for re-indexing API responses by item/user ID
Positions Modelecommercen/ai/models/AdvAIPagePositions.phpLegacy CRUD for ai_positions / ai_positions_mui tables
Admin Settingsecommercen/ai/controllers/AdvAdvisableAiSettings.phpAdmin CRUD for AI positions (create/update/delete with MUI)
XML Feedecommercen/ai/controllers/AdvAdvisableAIXml.phpProduct catalog XML feed consumed by the AI engine
Product Sync Jobecommercen/job/libraries/AdvAddProductsToAdvisableAi.phpScheduled job: ensure all products are in the AI feed lookup table
Front Controllerecommercen/core/Adv_front_controller.phpIntegration hub: customer setup, deferred event dispatch, recommendation fetching
Cart Controllerecommercen/api/controllers/AdvApiCartController.phpCart AJAX: add-to-cart events, after-add-to-cart recommendation responses
Circuit Breakersrc/CircuitBreaker/CircuitBreaker.phpState machine (closed/open/half-open) backed by L2 cache
Deferred Taskssrc/DeferredTask/DeferredTaskRunner.phpPriority queue executing closures within a time budget after response
Basket Track Typeapplication/core/BasketTrackType.phpEnum for recommendation attribution: aiRecommendation=1, smartRecommendation=2, relatedRecommendation=3, clientRecommendation=4

Modern Domain Layer (Admin CRUD)

ComponentPathPurpose
Position Entitysrc/Domains/Ai/Position/Repository/Entity.phpMaps ai_positions table
Position MUI Entitysrc/Domains/Ai/Position/Repository/MuiEntity.phpMaps ai_positions_mui table
Position Servicesrc/Domains/Ai/Position/Service.phpRead operations
Position WriteServicesrc/Domains/Ai/Position/WriteService.phpCreate/update/delete
Position REST Controllersrc/Rest/Ai/Controllers/Position.phpFull CRUD with OpenAPI annotations
ContentGeneration Entitysrc/Domains/Ai/ContentGeneration/Repository/Entity.phpMaps ai_content_generation table
ContentGeneration REST Controllersrc/Rest/Ai/Controllers/ContentGeneration.phpFull CRUD with OpenAPI annotations
Domain DIsrc/Domains/Ai/container.phpService registration for Position + ContentGeneration
REST DIsrc/Rest/Ai/container.phpController registration

REST API Endpoints (Admin)

MethodPathAuthDescription
GET/rest/ai/positionJWTList positions with filters/sort/pagination
GET/rest/ai/position/{id}JWTGet single position
GET/rest/ai/position/itemJWTGet single position by filter
POST/rest/ai/positionJWTCreate position
POST/rest/ai/position/{id}JWTUpdate position
DELETE/rest/ai/position/{id}JWTDelete position
GET/rest/ai/content-generationJWTList content generation logs
GET/rest/ai/content-generation/{id}JWTGet single log entry
GET/rest/ai/content-generation/itemJWTGet single log entry by filter
POST/rest/ai/content-generationJWTCreate log entry
POST/rest/ai/content-generation/{id}JWTUpdate log entry
DELETE/rest/ai/content-generation/{id}JWTDelete log entry

Code Flow 1: Product Catalog Sync

The AI engine needs the shop's product catalog to compute recommendations. Two mechanisms keep the catalog current:

Step 1 -- Feed Registration Job

The scheduled job AdvAddProductsToAdvisableAi runs on the cron framework and ensures every product in shop_product has a corresponding row in the shop_product_feed_lp lookup table for the Advisable AI feed ID.

AdvAddProductsToAdvisableAi::executeCommand()
  -> SELECT products missing from shop_product_feed_lp WHERE feed_id = XML_FEEDS['ADVISABLE_AI']
  -> INSERT BATCH into shop_product_feed_lp

This only registers products for inclusion; it does not transmit data to the AI engine.

Step 2 -- XML Feed Generation

The AI engine periodically polls the shop's XML feed endpoint, served by AdvAdvisableAIXml. The feed is gated by both ADVISABLE_AI.ENABLED and XML_FEEDS.IS_ENABLED_ADVISABLE_AI.

AdvAdvisableAIXml::index()
  -> getProductData()
     -> getFeedProductsForAdvisableAI()  // products in feed LP table
     -> getLiveProductsParsed()           // current prices/stock
     -> getProductsCodesParsed()          // SKU data
     -> getProductsCodeImagesParsed()     // product images
     -> merge VAT rates
  -> parseForXml() -> parseItems()
     -> For each product:
        -> selectCategory() -- picks deepest category by slug depth
        -> Build item: item_id, title, description, category, link, image_link,
           price, vat_rate, manufacturer, barcode, in_stock, availability, quantity, weight
  -> Feed -> OutputXml -> HTTP response

Feed item fields:

FieldSourceNotes
item_idshop_product.id (configurable via xmlDbIdKey['advisableAI'])Primary key for AI engine
titleshop_product_mui.nameFalls back to "UNDEFINED" if empty
descriptionshop_product_mui.descriptionFalls back to title
categoryDeepest shop_product_category_mui.category_idSelected by slug depth
linksite_url("$catSlug/$productSlug")Full product URL
image_linkassetUrl('files/products/' . mainImage)Primary image URL
pricefinal_price from live dataAfter discounts
vat_rateFrom shop_vats lookupTax rate
in_stockBooleanBased on stock > 0 or negative stock allowed
quantityStock level0 if out of stock
weightProduct weightOnly included if > 0

Products with final_price <= 0 are marked as out of stock regardless of actual stock level.

Step 3 -- Catalog Upload (External)

After fetching the XML, the AI engine can be instructed to create or validate a catalog via the admin connector:

AdvAdminAIConnector::createCatalog($url, $name)  // registers catalog URL
AdvAdminAIConnector::validateCatalog($url)        // validates catalog format

These are admin-only operations requiring bearer token authentication.


Code Flow 2: Customer Identity Management

Every storefront visitor needs an identity in the AI engine. The system manages three scenarios transparently.

Initialization (Every Page Load)

Adv_front_controller::__construct()
  -> setAdvisableAICustomer()
     -> loadAdvisableAI()
        -> shouldLoadAdvisableAI()
           -> Check env ADVISABLE_AI_FORCE_DISABLE
           -> Check is_robot()
           -> Check registry ADVISABLE_AI.ENABLED
     -> advisable_ai->setCustomer($customerSessionData)

Scenario A -- Anonymous Visitor (No Session, No Customer)

AdvAdvisableAI::createSessionUser()
  -> Generate UUID: "SESS_" + UuidV4
  -> ai_connector->createUser(sessionId, "*******")
  -> Store ai_user in session

Scenario B -- Returning Anonymous Visitor (Session Exists, No Customer)

Session already has ai_user -- no action needed, returns immediately.

Scenario C -- Logged-In Customer (Customer ID in Session)

AdvAdvisableAI::createUserFromCustomer()
  -> getOrCreateUser(customerId, customerName)
     -> ai_connector->getUser(customerId)
     -> If not found: ai_connector->createUser(customerId, customerName)
  -> Store ai_user in session

Guest customers get anonymized names ("*******").

Scenario D -- Session-to-Customer Transition (Login/Register)

When a visitor logs in or registers, their anonymous session behavior must be transferred:

Adv_customer::login/register
  -> switchAdvisableAIUser(customerId)
     -> [deferred] AdvAdvisableAI::sessionToCustomerSwitch(sessionUserId, customerId)
        -> getOrCreateUser(customerId)
        -> ai_connector->updateSessionToUserId(sessionUserId, aiUserId)
        -> Update session with new ai_user

Consistency Check

If a session has both ai_user and customer_id but they differ (e.g., after a different customer logs in on the same browser):

AdvAdvisableAI::checkCustomerUserConsistency()
  -> getOrCreateUser(customerId)
  -> sessionToCustomerSwitch(oldUserId, customerId)
  -> Update session

Code Flow 3: Behavioral Event Tracking

All storefront interactions are reported to the AI engine as deferred tasks, ensuring zero impact on page load time. Every event method follows this pattern:

Adv_front_controller::{action}ToAdvisableAI()
  -> shouldCallAdvisableAI()  // enabled + ai_user exists
  -> Capture variables (ai instance, userId, recommendationId)
  -> di()->get('deferred_task')->defer(
       task: fn() => $ai->{action}(...),
       maxCostSeconds: TIMEOUT + CONNECT_TIMEOUT,
       priority: NORMAL or CRITICAL,
       name: 'advisable_ai.{action}'
     )

Events Tracked

EventController MethodAI MethodPriorityRecommendation Attribution
Product viewviewToAdvisableAI()view()NORMALYes -- if navigated from recommendation
Add to cartreportAddToCartToAdvisableAI()addToCart()NORMALYes -- if added from recommendation
Remove from cartreportRemoveFromCartToAdvisableAI()removeFromCart()NORMALNo
Bookmark/wishlistbookmarkToAdvisableAI()bookmark()NORMALYes -- if bookmarked from recommendation
Remove bookmarkremoveBookmarkFromAdvisableAI()removeBookmark()NORMALNo
Product ratingratingToAdvisableAI()rating()NORMALYes -- if rated from recommendation
Purchase (order)purchaseToAdvisableAI()purchaseOrder()CRITICALYes -- per-item from shop_order_basket
Session switchswitchAdvisableAIUser()sessionToCustomerSwitch()NORMALNo

Recommendation Attribution Flow

When a customer clicks a recommended product, the URL includes query parameters:

  • rt (recommendation type) -- e.g., ai for AI recommendations
  • ri (recommendation ID) -- the specific recommendation batch ID

These are parsed on every page load:

Adv_front_controller::checkForRecommendationIdInQuery()
  -> recommendationTypeUsed = BasketTrackType::tryFromLabel(GET['rt'])
  -> recommendationIdUsed = GET['ri']

When the product is added to cart, the recommendation metadata is stored in the cart item options:

AdvApiCartController::cartHandle()
  -> options['recommendation'] = { id: recommendationId, type: recommendationType.value }

At order creation, the cart options are persisted to shop_order_basket:

Adv_order_model::createOrder()
  -> For each cart item:
     -> track_id = options.recommendation.id
     -> track_type = options.recommendation.type  (1 = AI, 2 = smart, 3 = related, 4 = client)

At purchase event submission, items with track_type = 1 (AI) include the recommendationId:

AdvAdvisableAI::purchaseOrder()
  -> Load order items via order_basket_model->getRecordForEcommerceJs()
  -> For each item where track_type == BasketTrackType::aiRecommendation():
     -> Include track_id as recommendationId
  -> ai_connector->batchPurchase(purchases)

Code Flow 4: Recommendation Fetching

Page-Level Recommendations

The primary entry point is Adv_front_controller::itemsRecommendationAI(), called from each page type:

itemsRecommendationAI(page, itemIds?)
  -> Check ADVISABLE_AI.ENABLED
  -> Load ai_page_positions_model
  -> getFrontPositionsWithMuiByPage(page) -- active positions for current page + language
  -> For each position:
     -> Route to algorithm by recommendation_type:
        MOST_POPULAR_ITEMS         -> mostPopularItems(ParameterBuilder)
        CUSTOMER_HISTORY           -> productsBasedOnCustomerHistory(limit)
        ITEMS_PURCHASED_WITH_ITEMS -> purchasedProductsWithProducts(ids, FbtParameterBuilder)
        ITEMS_PURCHASED_WITH_ITEM  -> purchasedProductsWithProduct(id, FbtParameterBuilder)
        SIMILAR_ITEMS_FOR_ITEM     -> similarItemsForItem(id, ParameterBuilder)
     -> Filter by min_items threshold
     -> Fetch full product data via product_model->getProductsByIdsOrderedFromInput()
     -> Merge recommendation metadata into product objects
  -> Store in $this->render['aiRecommendations']

Page-to-Position Mapping:

PageConstantCalling LocationItem Context
HomeAdvAIRecommendationPages::HOMEAdv_home::index()Cart product IDs (if any)
ProductAdvAIRecommendationPages::PRODUCTAdv_products::productAI()Current product ID
CartAdvAIRecommendationPages::CARTCart page renderCart product IDs
MinicartAdvAIRecommendationPages::MINICARTAdv_front_controller::minicartAI()No item IDs
CategoryAdvAIRecommendationPages::CATEGORYCategory page renderCart product IDs
CheckoutAdvAIRecommendationPages::CHECKOUTCheckout page renderCart product IDs

View-Level Position Slug Constants

Slug ConstantUsed On
RECOMMENDATION_HOME_HISTORY (home-history)Homepage
RECOMMENDATION_PRODUCT_BOUGHT (product-bought)Product page
RECOMMENDATION_PRODUCT_SIMILAR (product-similar-items)Product page
RECOMMENDATION_CART_HISTORY (cart-history)Cart page
RECOMMENDATION_MINICART_HISTORY (minicart-history)Minicart
RECOMMENDATION_CATEGORY_HISTORY (category-history)Category page

After-Add-to-Cart Recommendations

When a new product is added to the cart, the system can show a recommendation popup. An add-to-cart request with ?aatcr=1 triggers setCartRecommendationToJsonState(), which runs a first-match-wins cascade through the four configured algorithms (AdvisableAi, SmartRecommendations, RelatedRecommendations, Custom). The cascade dispatcher, per-algorithm methods, getInputOptions(), caching strategy, and AFTER_ADD_TO_CART registry keys are documented in AD-56 Recommendation Algorithm Priority.

History-Based Recommendation Caching

Customer history recommendations are cached per-session per-algorithm per-day:

productsBasedOnCustomerHistoryCore(limit, algo)
  -> If session has recommendation for this algo AND date == today:
     -> Use cached recommendationId
     -> Paginate: nextRecommendationItems(id, limit, skip)
     -> Increment skip counter in session
     -> Reset skip to 0 when reaching CUSTOMER_MAX_PRODUCTS
  -> Else:
     -> Fetch fresh: customerProductsBasedOnTheirHistory(userId, CustomerAlgorithmParameterBuilder)
     -> Cache { id, date, skip: 0 } in session

Algorithm variants (all map to the same core method with different algo parameter):

MethodAlgorithm CodeDescription
productsBasedOnCustomerHistory()autoEngine selects best algorithm
productsBasedOnCustomerHistoryVbf()vbfView-based filtering
productsBasedOnCustomerHistoryCbf()cbfContent-based filtering
productsBasedOnCustomerHistoryCf()cfCollaborative filtering
productsBasedOnCustomerHistoryMp()mpMost popular

View Rendering

Recommendations are rendered in PHP view templates using AdvAIRecommendationViewModel:

php
// In homepage.php, product.php, cart.php, category_products_list.php
$aiRecommendationProducts = AIRecommendationViewModel::filterProducts(
    $aiRecommendations[RECOMMENDATION_HOME_HISTORY]['products'] ?? [],
    $liveData, $productCodes, $productCodeImages
);
if (AIRecommendationViewModel::shouldBeDisplayed($position, $aiRecommendationProducts)) {
    // Render product slider with position title
}

Filtering rules:

  • shouldBeDisplayed(): product count >= position->min_items AND count > 0
  • filterProducts(): merges live pricing/stock data and removes products with availabilityStock <= 0

Product cards include recommendation tracking attributes for the Vue component:

html
<adv-button-view-buy
    recommendation-type="{{ recommendationType.label }}"
    recommendation-id="{{ recommendationId }}">
</adv-button-view-buy>

Code Flow 5: AI Content Generation

A separate subsystem within the ai/ module provides OpenAI-powered text generation for admin users.

AdvContentGeneration::generate(content, user, provider?)
  -> getDefaultProvider() or specified provider
  -> OpenAI::generateCompletion(content, user)
  -> Return generated text

Provider configuration (via registry AI_CONTENT_GENERATION_PROVIDER_OPEN_AI group):

KeyPurpose
IS_ENABLEDMaster toggle
API_KEYOpenAI API key
COMPLETIONS_API_URLAPI endpoint URL
MODELModel identifier
MAX_TOKENSMaximum response tokens
TEMPERATURECreativity parameter
TIMEOUTHTTP timeout

Admin prompt templates are configured via ai_content_generation_prompts config and AI_CONTENT_GENERATION_PROMPTS registry group. The aiContentGenerationTrait sets up the Vue admin state with provider status, prompts, and store context.

Content generation history is persisted in the ai_content_generation table and exposed via the REST API.


Data Model

ai_positions Table

ColumnTypeDescription
idINT AUTO_INCREMENTPrimary key
slugVARCHARPosition identifier (e.g., home-history, product-bought)
recommendation_typeVARCHARAlgorithm type (from AdvAIRecommendationTypes constants)
pageVARCHARPage placement (from AdvAIRecommendationPages constants)
min_itemsINTMinimum products required to display the position
request_limitINTMaximum products to request from the AI engine
activeBOOLEANWhether the position is enabled

ai_positions_mui Table

ColumnTypeDescription
idINT AUTO_INCREMENTPrimary key
position_idINTFK to ai_positions.id
titleVARCHARLocalized display title for the recommendation section
langVARCHAR(2)Language code

ai_content_generation Table

ColumnTypeDescription
idINT AUTO_INCREMENTPrimary key
userVARCHARAdmin user who generated the content
fieldVARCHAR NULLTarget field for the generated content
entity_typeVARCHAR NULLEntity type being generated for
promptVARCHAR NULLThe prompt sent to the AI
generated_textTEXTThe AI-generated response
entry_dateDATE NULLGeneration date
entry_datetimeDATETIME NULLGeneration timestamp
langVARCHAR(2)Language of the generated content

Tracking Columns in shop_order_basket

ColumnTypeDescription
track_idVARCHAR NULLRecommendation ID (the batch recommendation identifier from the AI engine)
track_typeINT NULLBasketTrackType enum value: 0=none, 1=AI, 2=smart, 3=related, 4=client

Feed Lookup Table shop_product_feed_lp

ColumnTypeDescription
product_idINTFK to shop_product.id
feed_idVARCHARFeed identifier (e.g., XML_FEEDS['ADVISABLE_AI'])

Configuration

Registry Keys (ADVISABLE_AI Group)

KeyTypePurpose
ENABLEDbooleanMaster toggle for all AI recommendation features
BASE_URIstringAdvisable AI API base URL
VERSIONstringAPI version string
USERNAMEstringAPI authentication username
PASSWORDstring (encrypted)API authentication password
APP_IDstringApplication identifier for the AI engine
APP_SECRETstring (encrypted)Application secret
TIMEOUTfloatHTTP request timeout in seconds
CONNECT_TIMEOUTfloatHTTP connection timeout in seconds
FBT_DAYSintFrequently-bought-together lookback period in days (default: 1)
CUSTOMER_MAX_PRODUCTSintMaximum products in history-based pagination (default: 50 via constant)
SHOW_ADMIN_DASHBOARDbooleanShow AI recommendation metrics on admin dashboard

Registry Keys (ALGO_PRIORITY Group)

The ALGO_PRIORITY registry group controls the cascade order for the after-add-to-cart and product-page-combo contexts. The full key catalog is in AD-56 Recommendation Algorithm Priority.

Circuit Breaker Configuration

Defined in application/config/circuit_breakers.php with env variable overrides:

Config KeyDefaultEnv Override
cb_advisable_ai_threshold3APP_CB_ADVISABLE_AI_THRESHOLD
cb_advisable_ai_cooldown_seconds30APP_CB_ADVISABLE_AI_COOLDOWN_SECONDS
cb_advisable_ai_max_cooldown_seconds300APP_CB_ADVISABLE_AI_MAX_COOLDOWN_SECONDS
cb_advisable_ai_cooldown_multiplier2.0APP_CB_ADVISABLE_AI_COOLDOWN_MULTIPLIER
cb_advisable_ai_state_ttl_buffer300APP_CB_ADVISABLE_AI_STATE_TTL_BUFFER

The circuit breaker is registered as circuit_breaker.advisable_ai in the DI container (application/config/container/circuit_breakers.php). State is persisted in the L2 cache adapter.

Logging

Configured in application/config/monolog.php under channel advisable-ai:

  • Log file: application/logs/advisable_ai_log.php
  • Default threshold: ERROR (overridable via ADVISABLE_AI_LOG_THRESHOLD env)
  • Rotation: 15 max files (overridable via ADVISABLE_AI_LOG_MAX_FILES env)
  • Features: Rotating file handler, introspection processor, multiline stacktrace

Query String Constants

Defined in application/config/constants.php:

ConstantValuePurpose
RECOMMENDATION_TYPE_QUERY_STRINGrtURL parameter for recommendation type label
RECOMMENDATION_ID_QUERY_STRINGriURL parameter for recommendation batch ID
AFTER_ADD_TO_CARD_RECOMMENDATIONaatcrURL parameter flag for after-add-to-cart recommendations
RECOMMENDATION_CUSTOMER_MAX_PRODUCTS50Default max products for history pagination

Recommendation Types Reference

These nine AdvAIRecommendationTypes values are stored in ai_positions.recommendation_type and route itemsRecommendationAI() to the correct connector call for each page position. Of these nine, only purchasedProductsWithProducts is also wired into the after-add-to-cart cascade — for the full cascade context see AD-56 Recommendation Algorithm Priority.

ConstantMethodInputAlgorithm
similarItemsForItemsimilarItemsForItem()Single product IDContent-based similarity
purchasedProductsWithProductpurchasedProductsWithProduct()Single product IDFrequently bought together (single)
purchasedProductsWithProductspurchasedProductsWithProducts()Array of product IDsFrequently bought together (multiple)
productsBasedOnCustomerHistoryproductsBasedOnCustomerHistory()Customer IDAuto-selected algorithm
productsBasedOnCustomerHistoryVbfproductsBasedOnCustomerHistoryVbf()Customer IDView-based filtering
productsBasedOnCustomerHistoryCbfproductsBasedOnCustomerHistoryCbf()Customer IDContent-based filtering
productsBasedOnCustomerHistoryCfproductsBasedOnCustomerHistoryCf()Customer IDCollaborative filtering
productsBasedOnCustomerHistoryMpproductsBasedOnCustomerHistoryMp()Customer IDMost popular items
mostPopularItemsmostPopularItems()NoneGlobal popularity

Additional AI Engine Queries (Available but Not Position-Mapped)

MethodPurpose
customersInterestedForItem()Get users likely interested in a given product (admin/B2B use)
getNextRecommendationItems()Paginate through an existing recommendation set
getRecommendationsCount()Get recommendation counts for reporting
getUserCategoriesRecommendation()Get recommended categories for a user

Circuit Breaker Behavior

The circuit breaker protects all AdvAdvisableAI methods from cascading failures:

AdvAdvisableAI::{anyMethod}()
  -> if !enabled || circuitBreaker?.isOpen(): return null/void
  -> try:
     -> Execute AI connector call
     -> circuitBreaker?.recordSuccess()
  -> catch Throwable:
     -> circuitBreaker?.recordFailure()
     -> Return null/void (graceful degradation)

State transitions:

  1. Closed (normal): All requests pass through. Failures increment counter.
  2. Open (tripped): After threshold (3) consecutive failures, all requests are short-circuited for cooldownSeconds (30s).
  3. Half-open: After cooldown, one test request is allowed. Success closes the breaker; failure re-opens with cooldownSeconds * cooldownMultiplier (up to maxCooldownSeconds of 300s).

The breaker is resolved lazily (circuitBreakerResolved flag) since it may not be registered in all environments.


Deferred Task Execution

All storefront AI interactions are executed after the HTTP response is sent:

DeferredTaskRunner::defer(
    task: Closure,
    maxCostSeconds: int,     // TIMEOUT + CONNECT_TIMEOUT from registry
    priority: int,           // CRITICAL (100) or NORMAL (50)
    name: string             // For logging, e.g., 'advisable_ai.view'
)

The DeferredTaskRunner uses an SplPriorityQueue and runs tasks within a configurable timeBudgetSeconds. Purchase events use PRIORITY_CRITICAL (100) to ensure they run before the time budget expires; all other events use PRIORITY_NORMAL (50).

Named deferred tasks:

  • advisable_ai.view -- product view
  • advisable_ai.add_to_cart -- cart addition
  • advisable_ai.remove_from_cart -- cart removal
  • advisable_ai.bookmark -- wishlist add
  • advisable_ai.remove_bookmark -- wishlist remove
  • advisable_ai.rating -- product rating
  • advisable_ai.purchase -- order purchase (CRITICAL)
  • advisable_ai.session_switch -- identity transfer on login

Client Extension Points

Position Management (Admin)

  • Legacy admin UI: AdvAdvisableAiSettings controller -- Vue-based CRUD for positions (requires AUTH_ROLE_ADVISABLE role)
  • REST API: Full CRUD at /rest/ai/position with OpenAPI documentation
  • Positions are multi-language (MUI) -- each position has per-language titles

After-Add-to-Cart Algorithm Priority

The after-add-to-cart cascade is configured in the AdvPlusAlgoPrioritySettings admin panel — drag-and-drop ordering, per-algorithm input modes, and related-group bindings. Full documentation: AD-56 Recommendation Algorithm Priority.

Product Sync

  • AdvAddProductsToAdvisableAi job ensures new products are registered for the AI feed
  • XML feed endpoint (AdvAdvisableAIXml) supports filtering by brand, category, top sellers, and stock level via query parameters

Custom Recommendations Hook

Client repos can override AdvApiCartController::getRecommendationCustom() to inject client-specific recommendation logic into the after-add-to-cart cascade. See AD-56 Recommendation Algorithm Priority for the Custom algorithm arm details and BasketTrackType::clientRecommendation() attribution.

Client Repo Overrides

In client repos, the AI module can be extended via:

  • Custom application/modules/job/libraries/AddProductsToAdvisableAi.php -- client-specific feed registration logic
  • Override getRecommendationCustom() in the client's cart controller
  • Custom position slugs and recommendation type mappings via admin UI
  • DI alias overrides in custom/Domains/container.php for repository configurators

Business Rules

RuleImplementation
Feature gatingADVISABLE_AI.ENABLED registry + ADVISABLE_AI_FORCE_DISABLE env
Bot exclusion$this->agent->is_robot() check in shouldLoadAdvisableAI()
Circuit breaker3-failure threshold, 30s initial cooldown, 2x multiplier, 300s max cooldown
Deferred executionAll storefront events are non-blocking via DeferredTaskRunner
Purchase priorityPurchase events get PRIORITY_CRITICAL (100) vs PRIORITY_NORMAL (50) for others
Daily recommendation rotationHistory-based recs cached in session with date check, reset at midnight
Pagination capCUSTOMER_MAX_PRODUCTS (default 50) limits how far skip advances before resetting
Min-items display thresholdEach position has min_items; fewer results hides the entire section
Stock filteringAdvAIRecommendationViewModel::filterProducts() removes out-of-stock items
Price filteringAdvAdvisableAIProductRecommendationParser::parse() removes items with price <= 0
Guest anonymizationGuest/anonymous customers get name "*******" in the AI engine
Recommendation attributiontrack_type + track_id persisted per order basket item for revenue reporting
Dashboard visibilityADVISABLE_AI.SHOW_ADMIN_DASHBOARD controls AI metrics on the admin dashboard
Admin role gatingAI settings admin requires AUTH_ROLE_ADVISABLE permission

See also: