set('discovery.decay.half_life_hours', 72); config()->set('discovery.weights.view', 1.0); $service = app(UserInterestProfileService::class); $user = User::factory()->create(); $contentType = ContentType::create([ 'name' => 'Digital Art', 'slug' => 'digital-art', 'description' => 'Digital artworks', ]); $categoryA = Category::create([ 'content_type_id' => $contentType->id, 'parent_id' => null, 'name' => 'Sci-Fi', 'slug' => 'sci-fi', 'description' => 'Sci-Fi category', 'is_active' => true, 'sort_order' => 0, ]); $categoryB = Category::create([ 'content_type_id' => $contentType->id, 'parent_id' => null, 'name' => 'Fantasy', 'slug' => 'fantasy', 'description' => 'Fantasy category', 'is_active' => true, 'sort_order' => 0, ]); $artworkA = Artwork::factory()->create(); $artworkB = Artwork::factory()->create(); $t0 = CarbonImmutable::parse('2026-02-14 00:00:00'); $service->applyEvent( userId: $user->id, eventType: 'view', artworkId: $artworkA->id, categoryId: $categoryA->id, occurredAt: $t0, eventId: '11111111-1111-1111-1111-111111111111', algoVersion: 'clip-cosine-v1' ); $service->applyEvent( userId: $user->id, eventType: 'view', artworkId: $artworkB->id, categoryId: $categoryB->id, occurredAt: $t0->addHours(72), eventId: '22222222-2222-2222-2222-222222222222', algoVersion: 'clip-cosine-v1' ); $profile = \App\Models\UserInterestProfile::query()->where('user_id', $user->id)->firstOrFail(); expect((int) $profile->event_count)->toBe(2); $normalized = (array) $profile->normalized_scores_json; expect($normalized)->toHaveKey('category:' . $categoryA->id); expect($normalized)->toHaveKey('category:' . $categoryB->id); expect((float) $normalized['category:' . $categoryA->id])->toBeGreaterThan(0.30)->toBeLessThan(0.35); expect((float) $normalized['category:' . $categoryB->id])->toBeGreaterThan(0.65)->toBeLessThan(0.70); });