Save workspace changes

This commit is contained in:
2026-04-18 17:02:56 +02:00
parent f02ea9a711
commit 87d60af5a9
4220 changed files with 1388603 additions and 1554 deletions

View File

@@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
namespace App\DTOs;
/**
* Lightweight value object representing a user's recommendation preference profile.
*
* Built by UserPreferenceBuilder from signals:
* - favourited artworks (+3)
* - awards given (+5)
* - creator follows (+2 for their tags)
* - own uploads (category bias)
*
* Cached in `user_reco_profiles` with a configurable TTL (default 6 hours).
*/
final class UserRecoProfileDTO
{
/**
* @param array<int, string> $topTagSlugs Top tag slugs by weighted score (up to 20)
* @param array<int, string> $topCategorySlugs Top category slugs (up to 5)
* @param array<int, int> $strongCreatorIds Followed creator user IDs (up to 50)
* @param array<string, float> $tagWeights Tag slug normalised weight (01)
* @param array<string, float> $categoryWeights Category slug normalised weight
* @param array<int, string> $dislikedTagSlugs Future: blocked/hidden tag slugs
*/
public function __construct(
public readonly array $topTagSlugs = [],
public readonly array $topCategorySlugs = [],
public readonly array $strongCreatorIds = [],
public readonly array $tagWeights = [],
public readonly array $categoryWeights = [],
public readonly array $dislikedTagSlugs = [],
) {}
/**
* True if the user has enough signals to drive personalised recommendations.
*/
public function hasSignals(): bool
{
return $this->topTagSlugs !== [] || $this->strongCreatorIds !== [];
}
/**
* Returns the normalised tag weight for a given slug (0.0 if unknown).
*/
public function tagWeight(string $slug): float
{
return (float) ($this->tagWeights[$slug] ?? 0.0);
}
/**
* Returns true when the creator is in the user's strong-follow list.
*/
public function followsCreator(int $userId): bool
{
return in_array($userId, $this->strongCreatorIds, true);
}
/**
* Serialise for storage in the DB / Redis cache.
*
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'top_tags' => $this->topTagSlugs,
'top_categories' => $this->topCategorySlugs,
'strong_creators' => $this->strongCreatorIds,
'tag_weights' => $this->tagWeights,
'category_weights' => $this->categoryWeights,
'disliked_tags' => $this->dislikedTagSlugs,
];
}
/**
* Re-hydrate from a stored array (e.g. from the DB JSON column).
*
* @param array<string, mixed> $data
*/
public static function fromArray(array $data): self
{
return new self(
topTagSlugs: (array) ($data['top_tags'] ?? []),
topCategorySlugs: (array) ($data['top_categories'] ?? []),
strongCreatorIds: array_map('intval', (array) ($data['strong_creators'] ?? [])),
tagWeights: array_map('floatval', (array) ($data['tag_weights'] ?? [])),
categoryWeights: array_map('floatval', (array) ($data['category_weights'] ?? [])),
dislikedTagSlugs: (array) ($data['disliked_tags'] ?? []),
);
}
}