env('RECOMMENDATIONS_QUEUE', env('VISION_QUEUE', 'default')), // ─── Phase 1 "For You" feed scoring weights ─────────────────────────────── // Influences the PHP reranking pass after Meilisearch candidate retrieval. // Tweak here without code changes. 'weights' => [ // Tag overlap score weight (0–1 normalized overlap fraction) 'tag_overlap' => (float) env('RECO_W_TAG_OVERLAP', 0.40), // Creator affinity score weight (1.0 if followed, 0 otherwise) 'creator_affinity' => (float) env('RECO_W_CREATOR_AFFINITY', 0.25), // Popularity boost (log-normalised views/downloads) 'popularity' => (float) env('RECO_W_POPULARITY', 0.20), // Freshness boost (exponential decay over 30 days) 'freshness' => (float) env('RECO_W_FRESHNESS', 0.15), ], // ─── User preference signal weights ────────────────────────────────────── // How much each user action contributes to building the reco profile. 'signal_weights' => [ 'award' => (float) env('RECO_SIG_AWARD', 5.0), 'favorite' => (float) env('RECO_SIG_FAVORITE', 3.0), 'reaction' => (float) env('RECO_SIG_REACTION', 2.0), 'view' => (float) env('RECO_SIG_VIEW', 1.0), 'follow' => (float) env('RECO_SIG_FOLLOW', 2.0), ], // ─── Candidate generation ────────────────────────────────────────────────── // How many Meilisearch candidates to fetch before PHP reranking. 'candidate_pool_size' => (int) env('RECO_CANDIDATE_POOL', 200), // ─── Diversity controls ──────────────────────────────────────────────────── // Maximum artworks per creator allowed in a single page of results. 'max_per_creator' => (int) env('RECO_MAX_PER_CREATOR', 3), // Minimum distinct tag count in first 20 feed results. 'min_unique_tags' => (int) env('RECO_MIN_UNIQUE_TAGS', 5), // ─── TTLs (seconds) ──────────────────────────────────────────────────────── 'ttl' => [ // User reco profile cache (tag/creator affinity data) 'user_reco_profile' => (int) env('RECO_TTL_PROFILE', 6 * 3600), // For You paginated results cache 'for_you_feed' => (int) env('RECO_TTL_FOR_YOU', 5 * 60), // Similar artworks per artwork 'similar_artworks' => (int) env('RECO_TTL_SIMILAR', 30 * 60), // Suggested creators per user 'creator_suggestions' => (int) env('RECO_TTL_CREATORS', 30 * 60), // Suggested tags per user 'tag_suggestions' => (int) env('RECO_TTL_TAGS', 60 * 60), ], // ─── Profile limits ──────────────────────────────────────────────────────── 'profile' => [ 'top_tags_limit' => (int) env('RECO_PROFILE_TAGS', 20), 'top_categories_limit' => (int) env('RECO_PROFILE_CATS', 5), 'strong_creators_limit' => (int) env('RECO_PROFILE_CREATORS', 50), ], 'embedding' => [ 'enabled' => env('RECOMMENDATIONS_EMBEDDING_ENABLED', true), 'model' => env('RECOMMENDATIONS_EMBEDDING_MODEL', 'clip'), 'model_version' => env('RECOMMENDATIONS_EMBEDDING_MODEL_VERSION', 'v1'), 'algo_version' => env('RECOMMENDATIONS_ALGO_VERSION', 'clip-cosine-v1'), // Preferred CLIP endpoint for embeddings. The service also accepts an embedding payload from the analyze endpoint response. 'endpoint' => env('CLIP_EMBED_ENDPOINT', '/embed'), 'timeout_seconds' => (int) env('CLIP_EMBED_TIMEOUT_SECONDS', 8), 'connect_timeout_seconds' => (int) env('CLIP_EMBED_CONNECT_TIMEOUT_SECONDS', 2), 'retries' => (int) env('CLIP_EMBED_HTTP_RETRIES', 1), 'retry_delay_ms' => (int) env('CLIP_EMBED_HTTP_RETRY_DELAY_MS', 200), // Guardrails for malformed service responses. 'min_dim' => (int) env('RECOMMENDATIONS_MIN_DIM', 64), 'max_dim' => (int) env('RECOMMENDATIONS_MAX_DIM', 4096), ], // Backfill chunk size for resumable queue fan-out. 'backfill_batch_size' => (int) env('RECOMMENDATIONS_BACKFILL_BATCH', 200), // A/B support for recommendation ranking variants. 'ab' => [ 'algo_versions' => array_values(array_filter(array_map( static fn (string $value): string => trim($value), explode(',', (string) env('RECOMMENDATIONS_AB_ALGO_VERSIONS', env('RECOMMENDATIONS_ALGO_VERSION', 'clip-cosine-v1'))) ))), ], // ─── Similar Artworks (hybrid recommender) ───────────────────────────────── 'similarity' => [ 'model_version' => env('SIMILARITY_MODEL_VERSION', 'sim_v1'), // Vector DB integration (behind feature flag) 'vector_enabled' => (bool) env('SIMILARITY_VECTOR_ENABLED', false), 'vector_adapter' => env('SIMILARITY_VECTOR_ADAPTER', 'pgvector'), // pgvector | pinecone // Hybrid blend weights (spec §5.4) 'weights_with_vector' => [ 'visual' => (float) env('SIM_W_VISUAL', 0.45), 'tag' => (float) env('SIM_W_TAG_VEC', 0.25), 'behavior' => (float) env('SIM_W_BEH_VEC', 0.20), 'category' => (float) env('SIM_W_CAT_VEC', 0.10), ], 'weights_without_vector' => [ 'tag' => (float) env('SIM_W_TAG', 0.55), 'behavior' => (float) env('SIM_W_BEH', 0.35), 'category' => (float) env('SIM_W_CAT', 0.10), ], // Diversity caps (spec §6) 'max_per_author' => (int) env('SIM_MAX_PER_AUTHOR', 2), 'result_limit' => (int) env('SIM_RESULT_LIMIT', 30), 'candidate_pool' => (int) env('SIM_CANDIDATE_POOL', 100), 'min_categories_top12' => (int) env('SIM_MIN_CATS_TOP12', 2), // Behavior pair building 'user_favourites_cap' => (int) env('SIM_USER_FAV_CAP', 50), // Cache TTL for precomputed lists (sec) 'cache_ttl' => (int) env('SIM_CACHE_TTL', 6 * 3600), // Pinecone adapter settings 'pinecone' => [ 'api_key' => env('PINECONE_API_KEY'), 'index_host' => env('PINECONE_INDEX_HOST'), 'index_name' => env('PINECONE_INDEX_NAME', 'skinbase-artworks'), 'namespace' => env('PINECONE_NAMESPACE', ''), 'top_k' => (int) env('PINECONE_TOP_K', 100), ], ], ];