optimizations
This commit is contained in:
111
tests/Feature/Admin/DiscoveryFeedbackReportTest.php
Normal file
111
tests/Feature/Admin/DiscoveryFeedbackReportTest.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Models\Artwork;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('admin discovery feedback report includes negative feedback and undo metrics', function () {
|
||||
$admin = User::factory()->create(['role' => 'admin']);
|
||||
$user = User::factory()->create();
|
||||
$artwork = Artwork::factory()->create();
|
||||
$previousDate = now()->subDay()->toDateString();
|
||||
$date = now()->toDateString();
|
||||
|
||||
$recordEvent = function (string $eventDate, string $eventType, array $meta = []) use ($user, $artwork) {
|
||||
$algoVersion = (string) ($meta['algo_version'] ?? 'clip-cosine-v2-adaptive');
|
||||
unset($meta['algo_version']);
|
||||
|
||||
DB::table('user_discovery_events')->insert([
|
||||
'event_id' => (string) Str::uuid(),
|
||||
'user_id' => $user->id,
|
||||
'artwork_id' => $artwork->id,
|
||||
'category_id' => null,
|
||||
'event_type' => $eventType,
|
||||
'event_version' => 'event-v1',
|
||||
'algo_version' => $algoVersion,
|
||||
'weight' => 1.0,
|
||||
'event_date' => $eventDate,
|
||||
'occurred_at' => now(),
|
||||
'meta' => json_encode(array_merge([
|
||||
'gallery_type' => 'for-you',
|
||||
'surface' => 'for-you',
|
||||
], $meta), JSON_THROW_ON_ERROR),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
};
|
||||
|
||||
$recordEvent($previousDate, 'view');
|
||||
$recordEvent($previousDate, 'click');
|
||||
$recordEvent($previousDate, 'favorite');
|
||||
$recordEvent($previousDate, 'hide_artwork', ['reason' => 'not_relevant']);
|
||||
$recordEvent($previousDate, 'dislike_tag', ['tag_slug' => 'abstract']);
|
||||
$recordEvent($previousDate, 'view', ['gallery_type' => 'homepage', 'surface' => 'homepage', 'algo_version' => 'clip-cosine-v1']);
|
||||
$recordEvent($previousDate, 'click', ['gallery_type' => 'homepage', 'surface' => 'homepage', 'algo_version' => 'clip-cosine-v1']);
|
||||
$recordEvent($previousDate, 'favorite', ['gallery_type' => 'homepage', 'surface' => 'homepage', 'algo_version' => 'clip-cosine-v1']);
|
||||
|
||||
$recordEvent($date, 'view');
|
||||
$recordEvent($date, 'click');
|
||||
$recordEvent($date, 'favorite');
|
||||
$recordEvent($date, 'download');
|
||||
$recordEvent($date, 'hide_artwork', ['reason' => 'not_relevant']);
|
||||
$recordEvent($date, 'unhide_artwork', ['reason' => 'undo']);
|
||||
$recordEvent($date, 'view', ['gallery_type' => 'homepage', 'surface' => 'homepage', 'algo_version' => 'clip-cosine-v1']);
|
||||
$recordEvent($date, 'click', ['gallery_type' => 'homepage', 'surface' => 'homepage', 'algo_version' => 'clip-cosine-v1']);
|
||||
$recordEvent($date, 'hide_artwork', ['gallery_type' => 'homepage', 'surface' => 'homepage', 'algo_version' => 'clip-cosine-v1', 'reason' => 'not_relevant']);
|
||||
$recordEvent($date, 'dislike_tag', ['gallery_type' => 'homepage', 'surface' => 'homepage', 'algo_version' => 'clip-cosine-v1', 'tag_slug' => 'portrait']);
|
||||
|
||||
$this->artisan('analytics:aggregate-discovery-feedback', ['--date' => $previousDate])
|
||||
->assertExitCode(0);
|
||||
$this->artisan('analytics:aggregate-discovery-feedback', ['--date' => $date])
|
||||
->assertExitCode(0);
|
||||
|
||||
$response = $this->actingAs($admin)
|
||||
->getJson('/api/admin/reports/discovery-feedback?from=' . $previousDate . '&to=' . $date . '&limit=10');
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonPath('overview.views', 4);
|
||||
$response->assertJsonPath('overview.clicks', 4);
|
||||
$response->assertJsonPath('overview.feedback_actions', 4);
|
||||
$response->assertJsonPath('overview.hidden_artworks', 3);
|
||||
$response->assertJsonPath('overview.disliked_tags', 2);
|
||||
$response->assertJsonPath('overview.negative_feedback_actions', 5);
|
||||
$response->assertJsonPath('overview.undo_hidden_artworks', 1);
|
||||
$response->assertJsonPath('overview.undo_disliked_tags', 0);
|
||||
$response->assertJsonPath('overview.undo_actions', 1);
|
||||
$response->assertJsonPath('daily_feedback.0.negative_feedback_actions', 2);
|
||||
$response->assertJsonPath('daily_feedback.1.negative_feedback_actions', 3);
|
||||
$response->assertJsonPath('daily_feedback.1.undo_actions', 1);
|
||||
$response->assertJsonPath('trend_summary.latest_day.date', $date);
|
||||
$response->assertJsonPath('trend_summary.previous_day.date', $previousDate);
|
||||
$response->assertJsonPath('trend_summary.rolling_7d_average.feedback_actions', 2);
|
||||
$response->assertJsonPath('trend_summary.rolling_7d_average.negative_feedback_actions', 2.5);
|
||||
$response->assertJsonPath('trend_summary.rolling_7d_average.undo_actions', 0.5);
|
||||
$response->assertJsonPath('trend_summary.deltas.feedback_actions.label', 'Flat');
|
||||
$response->assertJsonPath('trend_summary.deltas.negative_feedback_actions.label', 'Worse +1 vs prev day');
|
||||
$response->assertJsonPath('trend_summary.overall_status.level', 'watch');
|
||||
$response->assertJsonPath('by_surface.0.surface', 'homepage');
|
||||
$response->assertJsonPath('by_surface.0.negative_feedback_actions', 2);
|
||||
$response->assertJsonPath('by_surface.0.trend.overall_status.level', 'risk');
|
||||
$response->assertJsonPath('by_surface.0.trend.deltas.feedback_actions.label', 'Worse -1 vs prev day');
|
||||
$response->assertJsonPath('by_surface.0.trend.deltas.negative_feedback_actions.label', 'Worse +2 vs prev day');
|
||||
$response->assertJsonPath('by_surface.1.surface', 'for-you');
|
||||
$response->assertJsonPath('by_surface.1.trend.overall_status.level', 'healthy');
|
||||
$response->assertJsonPath('by_algo_surface.0.algo_version', 'clip-cosine-v1');
|
||||
$response->assertJsonPath('by_algo_surface.0.surface', 'homepage');
|
||||
$response->assertJsonPath('by_algo_surface.0.negative_feedback_actions', 2);
|
||||
$response->assertJsonPath('by_algo_surface.0.trend.overall_status.level', 'risk');
|
||||
$response->assertJsonPath('by_algo_surface.0.trend.deltas.feedback_actions.label', 'Worse -1 vs prev day');
|
||||
$response->assertJsonPath('by_algo_surface.0.trend.deltas.negative_feedback_actions.label', 'Worse +2 vs prev day');
|
||||
$response->assertJsonPath('by_algo_surface.1.algo_version', 'clip-cosine-v2-adaptive');
|
||||
$response->assertJsonPath('top_artworks.0.artwork_id', $artwork->id);
|
||||
$response->assertJsonPath('top_artworks.0.negative_feedback_actions', 3);
|
||||
$response->assertJsonPath('top_artworks.0.undo_actions', 1);
|
||||
$response->assertJsonPath('latest_aggregated_date', $date);
|
||||
});
|
||||
55
tests/Feature/Admin/FeedEngineDecisionReportTest.php
Normal file
55
tests/Feature/Admin/FeedEngineDecisionReportTest.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('admin can inspect feed engine decisions for a user bucket', function () {
|
||||
config()->set('discovery.v2.enabled', true);
|
||||
config()->set('discovery.v2.rollout_percentage', 35);
|
||||
config()->set('discovery.v2.algo_version', 'clip-cosine-v2-adaptive');
|
||||
|
||||
$admin = User::factory()->create(['role' => 'admin']);
|
||||
$subject = User::factory()->create();
|
||||
$expectedBucket = abs((int) crc32((string) $subject->id)) % 100;
|
||||
|
||||
$response = $this->actingAs($admin)
|
||||
->getJson('/api/admin/reports/feed-engine-decision?user_id=' . $subject->id);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonPath('decision.user_id', $subject->id);
|
||||
$response->assertJsonPath('decision.bucket', $expectedBucket);
|
||||
$response->assertJsonPath('decision.rollout_percentage', 35);
|
||||
$response->assertJsonPath('decision.uses_v2', $expectedBucket < 35);
|
||||
$response->assertJsonPath('decision.selected_engine', $expectedBucket < 35 ? 'v2' : 'v1');
|
||||
});
|
||||
|
||||
it('admin can inspect explicit v2 algo overrides even when rollout is disabled', function () {
|
||||
config()->set('discovery.v2.enabled', false);
|
||||
config()->set('discovery.v2.rollout_percentage', 0);
|
||||
config()->set('discovery.v2.algo_version', 'clip-cosine-v2-adaptive');
|
||||
|
||||
$admin = User::factory()->create(['role' => 'admin']);
|
||||
$subject = User::factory()->create();
|
||||
|
||||
$response = $this->actingAs($admin)
|
||||
->getJson('/api/admin/reports/feed-engine-decision?user_id=' . $subject->id . '&algo_version=clip-cosine-v2-adaptive');
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonPath('decision.uses_v2', true);
|
||||
$response->assertJsonPath('decision.selected_engine', 'v2');
|
||||
$response->assertJsonPath('decision.reason', 'explicit_algo_override');
|
||||
});
|
||||
|
||||
it('non-admin is denied feed engine decision endpoint', function () {
|
||||
$user = User::factory()->create(['role' => 'user']);
|
||||
$subject = User::factory()->create();
|
||||
|
||||
$response = $this->actingAs($user)
|
||||
->getJson('/api/admin/reports/feed-engine-decision?user_id=' . $subject->id);
|
||||
|
||||
$response->assertStatus(403);
|
||||
});
|
||||
Reference in New Issue
Block a user