Appearance
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:
- Advisable AI Recommendations -- external ML engine for product recommendations (this flow)
- 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.ENABLEDregistry key - Can be force-disabled per-environment via
ADVISABLE_AI_FORCE_DISABLEenv 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
DeferredTaskRunnerto avoid impacting page load time - Recommendation tracking flows through the full purchase funnel:
track_typeandtrack_idare persisted toshop_order_basketrows for revenue attribution - History-based recommendations cache the recommendation ID in the session, rotating once per day
- Pagination through recommendation results uses a
skipcounter stored in session, capped byADVISABLE_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
| Component | Path | Purpose |
|---|---|---|
| External Package | vendor/ecommercen/ai-connector/ | HTTP client, DTOs, query builders for the Advisable AI REST API |
| Core AI Connector | ecommercen/ai/libraries/CoreAIConnector.php | Base class: config loading, Monolog logger, timestamp formatting |
| AI Connector | ecommercen/ai/libraries/AdvAIConnector.php | Full HTTP client wrapping the external package for all event and recommendation calls |
| Admin AI Connector | ecommercen/ai/libraries/AdvAdminAIConnector.php | Admin operations: create/delete clients and apps with bearer token auth |
| Business Wrapper | ecommercen/ai/libraries/AdvAdvisableAI.php | High-level facade with circuit breaker, customer user management, product ID resolution |
| Recommendation Parser | ecommercen/ai/libraries/AdvAdvisableAIProductRecommendationParser.php | Converts ItemsRecommendation responses to product card objects with track type/ID |
| View Model | ecommercen/ai/libraries/AdvAIRecommendationViewModel.php | Display logic: min-item checks, stock filtering, live data merge |
| Page Positions | ecommercen/ai/libraries/AdvAIRecommendationPages.php | Constants: HOME, CART, MINICART, PRODUCT, CATEGORY, CHECKOUT |
| Recommendation Types | ecommercen/ai/libraries/AdvAIRecommendationTypes.php | 9 algorithm types (similar items, FBT, history-based variants, most popular) |
| Response Transformers | ecommercen/ai/libraries/AiRecommendation*Transformer.php | Trait + interface for re-indexing API responses by item/user ID |
| Positions Model | ecommercen/ai/models/AdvAIPagePositions.php | Legacy CRUD for ai_positions / ai_positions_mui tables |
| Admin Settings | ecommercen/ai/controllers/AdvAdvisableAiSettings.php | Admin CRUD for AI positions (create/update/delete with MUI) |
| XML Feed | ecommercen/ai/controllers/AdvAdvisableAIXml.php | Product catalog XML feed consumed by the AI engine |
| Product Sync Job | ecommercen/job/libraries/AdvAddProductsToAdvisableAi.php | Scheduled job: ensure all products are in the AI feed lookup table |
| Front Controller | ecommercen/core/Adv_front_controller.php | Integration hub: customer setup, deferred event dispatch, recommendation fetching |
| Cart Controller | ecommercen/api/controllers/AdvApiCartController.php | Cart AJAX: add-to-cart events, after-add-to-cart recommendation responses |
| Circuit Breaker | src/CircuitBreaker/CircuitBreaker.php | State machine (closed/open/half-open) backed by L2 cache |
| Deferred Tasks | src/DeferredTask/DeferredTaskRunner.php | Priority queue executing closures within a time budget after response |
| Basket Track Type | application/core/BasketTrackType.php | Enum for recommendation attribution: aiRecommendation=1, smartRecommendation=2, relatedRecommendation=3, clientRecommendation=4 |
Modern Domain Layer (Admin CRUD)
| Component | Path | Purpose |
|---|---|---|
| Position Entity | src/Domains/Ai/Position/Repository/Entity.php | Maps ai_positions table |
| Position MUI Entity | src/Domains/Ai/Position/Repository/MuiEntity.php | Maps ai_positions_mui table |
| Position Service | src/Domains/Ai/Position/Service.php | Read operations |
| Position WriteService | src/Domains/Ai/Position/WriteService.php | Create/update/delete |
| Position REST Controller | src/Rest/Ai/Controllers/Position.php | Full CRUD with OpenAPI annotations |
| ContentGeneration Entity | src/Domains/Ai/ContentGeneration/Repository/Entity.php | Maps ai_content_generation table |
| ContentGeneration REST Controller | src/Rest/Ai/Controllers/ContentGeneration.php | Full CRUD with OpenAPI annotations |
| Domain DI | src/Domains/Ai/container.php | Service registration for Position + ContentGeneration |
| REST DI | src/Rest/Ai/container.php | Controller registration |
REST API Endpoints (Admin)
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /rest/ai/position | JWT | List positions with filters/sort/pagination |
| GET | /rest/ai/position/{id} | JWT | Get single position |
| GET | /rest/ai/position/item | JWT | Get single position by filter |
| POST | /rest/ai/position | JWT | Create position |
| POST | /rest/ai/position/{id} | JWT | Update position |
| DELETE | /rest/ai/position/{id} | JWT | Delete position |
| GET | /rest/ai/content-generation | JWT | List content generation logs |
| GET | /rest/ai/content-generation/{id} | JWT | Get single log entry |
| GET | /rest/ai/content-generation/item | JWT | Get single log entry by filter |
| POST | /rest/ai/content-generation | JWT | Create log entry |
| POST | /rest/ai/content-generation/{id} | JWT | Update log entry |
| DELETE | /rest/ai/content-generation/{id} | JWT | Delete 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_lpThis 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 responseFeed item fields:
| Field | Source | Notes |
|---|---|---|
item_id | shop_product.id (configurable via xmlDbIdKey['advisableAI']) | Primary key for AI engine |
title | shop_product_mui.name | Falls back to "UNDEFINED" if empty |
description | shop_product_mui.description | Falls back to title |
category | Deepest shop_product_category_mui.category_id | Selected by slug depth |
link | site_url("$catSlug/$productSlug") | Full product URL |
image_link | assetUrl('files/products/' . mainImage) | Primary image URL |
price | final_price from live data | After discounts |
vat_rate | From shop_vats lookup | Tax rate |
in_stock | Boolean | Based on stock > 0 or negative stock allowed |
quantity | Stock level | 0 if out of stock |
weight | Product weight | Only 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 formatThese 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 sessionScenario 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 sessionGuest 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_userConsistency 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 sessionCode 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
| Event | Controller Method | AI Method | Priority | Recommendation Attribution |
|---|---|---|---|---|
| Product view | viewToAdvisableAI() | view() | NORMAL | Yes -- if navigated from recommendation |
| Add to cart | reportAddToCartToAdvisableAI() | addToCart() | NORMAL | Yes -- if added from recommendation |
| Remove from cart | reportRemoveFromCartToAdvisableAI() | removeFromCart() | NORMAL | No |
| Bookmark/wishlist | bookmarkToAdvisableAI() | bookmark() | NORMAL | Yes -- if bookmarked from recommendation |
| Remove bookmark | removeBookmarkFromAdvisableAI() | removeBookmark() | NORMAL | No |
| Product rating | ratingToAdvisableAI() | rating() | NORMAL | Yes -- if rated from recommendation |
| Purchase (order) | purchaseToAdvisableAI() | purchaseOrder() | CRITICAL | Yes -- per-item from shop_order_basket |
| Session switch | switchAdvisableAIUser() | sessionToCustomerSwitch() | NORMAL | No |
Recommendation Attribution Flow
When a customer clicks a recommended product, the URL includes query parameters:
rt(recommendation type) -- e.g.,aifor AI recommendationsri(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:
| Page | Constant | Calling Location | Item Context |
|---|---|---|---|
| Home | AdvAIRecommendationPages::HOME | Adv_home::index() | Cart product IDs (if any) |
| Product | AdvAIRecommendationPages::PRODUCT | Adv_products::productAI() | Current product ID |
| Cart | AdvAIRecommendationPages::CART | Cart page render | Cart product IDs |
| Minicart | AdvAIRecommendationPages::MINICART | Adv_front_controller::minicartAI() | No item IDs |
| Category | AdvAIRecommendationPages::CATEGORY | Category page render | Cart product IDs |
| Checkout | AdvAIRecommendationPages::CHECKOUT | Checkout page render | Cart product IDs |
View-Level Position Slug Constants
| Slug Constant | Used 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 sessionAlgorithm variants (all map to the same core method with different algo parameter):
| Method | Algorithm Code | Description |
|---|---|---|
productsBasedOnCustomerHistory() | auto | Engine selects best algorithm |
productsBasedOnCustomerHistoryVbf() | vbf | View-based filtering |
productsBasedOnCustomerHistoryCbf() | cbf | Content-based filtering |
productsBasedOnCustomerHistoryCf() | cf | Collaborative filtering |
productsBasedOnCustomerHistoryMp() | mp | Most 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_itemsAND count > 0filterProducts(): merges live pricing/stock data and removes products withavailabilityStock <= 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 textProvider configuration (via registry AI_CONTENT_GENERATION_PROVIDER_OPEN_AI group):
| Key | Purpose |
|---|---|
IS_ENABLED | Master toggle |
API_KEY | OpenAI API key |
COMPLETIONS_API_URL | API endpoint URL |
MODEL | Model identifier |
MAX_TOKENS | Maximum response tokens |
TEMPERATURE | Creativity parameter |
TIMEOUT | HTTP 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
| Column | Type | Description |
|---|---|---|
id | INT AUTO_INCREMENT | Primary key |
slug | VARCHAR | Position identifier (e.g., home-history, product-bought) |
recommendation_type | VARCHAR | Algorithm type (from AdvAIRecommendationTypes constants) |
page | VARCHAR | Page placement (from AdvAIRecommendationPages constants) |
min_items | INT | Minimum products required to display the position |
request_limit | INT | Maximum products to request from the AI engine |
active | BOOLEAN | Whether the position is enabled |
ai_positions_mui Table
| Column | Type | Description |
|---|---|---|
id | INT AUTO_INCREMENT | Primary key |
position_id | INT | FK to ai_positions.id |
title | VARCHAR | Localized display title for the recommendation section |
lang | VARCHAR(2) | Language code |
ai_content_generation Table
| Column | Type | Description |
|---|---|---|
id | INT AUTO_INCREMENT | Primary key |
user | VARCHAR | Admin user who generated the content |
field | VARCHAR NULL | Target field for the generated content |
entity_type | VARCHAR NULL | Entity type being generated for |
prompt | VARCHAR NULL | The prompt sent to the AI |
generated_text | TEXT | The AI-generated response |
entry_date | DATE NULL | Generation date |
entry_datetime | DATETIME NULL | Generation timestamp |
lang | VARCHAR(2) | Language of the generated content |
Tracking Columns in shop_order_basket
| Column | Type | Description |
|---|---|---|
track_id | VARCHAR NULL | Recommendation ID (the batch recommendation identifier from the AI engine) |
track_type | INT NULL | BasketTrackType enum value: 0=none, 1=AI, 2=smart, 3=related, 4=client |
Feed Lookup Table shop_product_feed_lp
| Column | Type | Description |
|---|---|---|
product_id | INT | FK to shop_product.id |
feed_id | VARCHAR | Feed identifier (e.g., XML_FEEDS['ADVISABLE_AI']) |
Configuration
Registry Keys (ADVISABLE_AI Group)
| Key | Type | Purpose |
|---|---|---|
ENABLED | boolean | Master toggle for all AI recommendation features |
BASE_URI | string | Advisable AI API base URL |
VERSION | string | API version string |
USERNAME | string | API authentication username |
PASSWORD | string (encrypted) | API authentication password |
APP_ID | string | Application identifier for the AI engine |
APP_SECRET | string (encrypted) | Application secret |
TIMEOUT | float | HTTP request timeout in seconds |
CONNECT_TIMEOUT | float | HTTP connection timeout in seconds |
FBT_DAYS | int | Frequently-bought-together lookback period in days (default: 1) |
CUSTOMER_MAX_PRODUCTS | int | Maximum products in history-based pagination (default: 50 via constant) |
SHOW_ADMIN_DASHBOARD | boolean | Show 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 Key | Default | Env Override |
|---|---|---|
cb_advisable_ai_threshold | 3 | APP_CB_ADVISABLE_AI_THRESHOLD |
cb_advisable_ai_cooldown_seconds | 30 | APP_CB_ADVISABLE_AI_COOLDOWN_SECONDS |
cb_advisable_ai_max_cooldown_seconds | 300 | APP_CB_ADVISABLE_AI_MAX_COOLDOWN_SECONDS |
cb_advisable_ai_cooldown_multiplier | 2.0 | APP_CB_ADVISABLE_AI_COOLDOWN_MULTIPLIER |
cb_advisable_ai_state_ttl_buffer | 300 | APP_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 viaADVISABLE_AI_LOG_THRESHOLDenv) - Rotation: 15 max files (overridable via
ADVISABLE_AI_LOG_MAX_FILESenv) - Features: Rotating file handler, introspection processor, multiline stacktrace
Query String Constants
Defined in application/config/constants.php:
| Constant | Value | Purpose |
|---|---|---|
RECOMMENDATION_TYPE_QUERY_STRING | rt | URL parameter for recommendation type label |
RECOMMENDATION_ID_QUERY_STRING | ri | URL parameter for recommendation batch ID |
AFTER_ADD_TO_CARD_RECOMMENDATION | aatcr | URL parameter flag for after-add-to-cart recommendations |
RECOMMENDATION_CUSTOMER_MAX_PRODUCTS | 50 | Default 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.
| Constant | Method | Input | Algorithm |
|---|---|---|---|
similarItemsForItem | similarItemsForItem() | Single product ID | Content-based similarity |
purchasedProductsWithProduct | purchasedProductsWithProduct() | Single product ID | Frequently bought together (single) |
purchasedProductsWithProducts | purchasedProductsWithProducts() | Array of product IDs | Frequently bought together (multiple) |
productsBasedOnCustomerHistory | productsBasedOnCustomerHistory() | Customer ID | Auto-selected algorithm |
productsBasedOnCustomerHistoryVbf | productsBasedOnCustomerHistoryVbf() | Customer ID | View-based filtering |
productsBasedOnCustomerHistoryCbf | productsBasedOnCustomerHistoryCbf() | Customer ID | Content-based filtering |
productsBasedOnCustomerHistoryCf | productsBasedOnCustomerHistoryCf() | Customer ID | Collaborative filtering |
productsBasedOnCustomerHistoryMp | productsBasedOnCustomerHistoryMp() | Customer ID | Most popular items |
mostPopularItems | mostPopularItems() | None | Global popularity |
Additional AI Engine Queries (Available but Not Position-Mapped)
| Method | Purpose |
|---|---|
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:
- Closed (normal): All requests pass through. Failures increment counter.
- Open (tripped): After
threshold(3) consecutive failures, all requests are short-circuited forcooldownSeconds(30s). - Half-open: After cooldown, one test request is allowed. Success closes the breaker; failure re-opens with
cooldownSeconds * cooldownMultiplier(up tomaxCooldownSecondsof 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 viewadvisable_ai.add_to_cart-- cart additionadvisable_ai.remove_from_cart-- cart removaladvisable_ai.bookmark-- wishlist addadvisable_ai.remove_bookmark-- wishlist removeadvisable_ai.rating-- product ratingadvisable_ai.purchase-- order purchase (CRITICAL)advisable_ai.session_switch-- identity transfer on login
Client Extension Points
Position Management (Admin)
- Legacy admin UI:
AdvAdvisableAiSettingscontroller -- Vue-based CRUD for positions (requiresAUTH_ROLE_ADVISABLErole) - REST API: Full CRUD at
/rest/ai/positionwith 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
AdvAddProductsToAdvisableAijob 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.phpfor repository configurators
Business Rules
| Rule | Implementation |
|---|---|
| Feature gating | ADVISABLE_AI.ENABLED registry + ADVISABLE_AI_FORCE_DISABLE env |
| Bot exclusion | $this->agent->is_robot() check in shouldLoadAdvisableAI() |
| Circuit breaker | 3-failure threshold, 30s initial cooldown, 2x multiplier, 300s max cooldown |
| Deferred execution | All storefront events are non-blocking via DeferredTaskRunner |
| Purchase priority | Purchase events get PRIORITY_CRITICAL (100) vs PRIORITY_NORMAL (50) for others |
| Daily recommendation rotation | History-based recs cached in session with date check, reset at midnight |
| Pagination cap | CUSTOMER_MAX_PRODUCTS (default 50) limits how far skip advances before resetting |
| Min-items display threshold | Each position has min_items; fewer results hides the entire section |
| Stock filtering | AdvAIRecommendationViewModel::filterProducts() removes out-of-stock items |
| Price filtering | AdvAdvisableAIProductRecommendationParser::parse() removes items with price <= 0 |
| Guest anonymization | Guest/anonymous customers get name "*******" in the AI engine |
| Recommendation attribution | track_type + track_id persisted per order basket item for revenue reporting |
| Dashboard visibility | ADVISABLE_AI.SHOW_ADMIN_DASHBOARD controls AI metrics on the admin dashboard |
| Admin role gating | AI settings admin requires AUTH_ROLE_ADVISABLE permission |
Related Flows
- CF-01 Product Browsing -- category page AI placements
- CF-02 Product Detail -- product page AI placements (bought-with, similar)
- CF-05 Cart Management -- recommendation tracking in cart items, after-add-to-cart recommendations
- CF-26 Home Page -- home page AI placements
- AD-10 Reporting -- recommendation revenue attribution dashboard
- SY-01 Cron Framework -- scheduled
AdvAddProductsToAdvisableAijob - AD-56 Recommendation Algorithm Priority -- admin configuration of recommendation algorithm priority and fallback chain
See also:
- Advisable AI guide -- setup and configuration reference
- SY-23 MUI Translation Pattern --
ai_positions_muicompanion table stores per-language position names for admin UI - SY-26 Circuit Breaker -- circuit breaker state machine protecting all
AdvAdvisableAIcalls from cascading failures - SY-27 Deferred Task Runner -- post-response task execution used to defer AI signal tracking calls
- Circuit Breaker guide -- circuit breaker pattern documentation
- Deferred Task guide -- deferred task runner documentation