112 lines
6.3 KiB
PHP
112 lines
6.3 KiB
PHP
<?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);
|
|
});
|