Files
SkinbaseNova/config/recommendations.php
Gregor Klevze 67ef79766c fix(gallery): fill tall portrait cards to full block width with object-cover crop
- ArtworkCard: add w-full to nova-card-media, use absolute inset-0 on img so
  object-cover fills the max-height capped box instead of collapsing the width
- MasonryGallery.css: add width:100% to media container, position img
  absolutely so top/bottom is cropped rather than leaving dark gaps
- Add React MasonryGallery + ArtworkCard components and entry point
- Add recommendation system: UserRecoProfile model/DTO/migration,
  SuggestedCreatorsController, SuggestedTagsController, Recommendation
  services, config/recommendations.php
- SimilarArtworksController, DiscoverController, HomepageService updates
- Update routes (api + web) and discover/for-you views
- Refresh favicon assets, update vite.config.js
2026-02-27 13:34:08 +01:00

93 lines
5.1 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
return [
// Uses same queue family as vision jobs by default; keeps embedding work async and non-blocking.
'queue' => 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 (01 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')))
))),
],
];