feat: Inertia profile settings page, Studio edit redesign, EGS, Nova UI components\n\n- Redesign /dashboard/profile as Inertia React page (Settings/ProfileEdit)\n with SettingsLayout sidebar, Nova UI components (TextInput, Textarea,\n Toggle, Select, RadioGroup, Modal, Button), avatar drag-and-drop,\n password change, and account deletion sections\n- Redesign Studio artwork edit page with two-column layout, Nova components,\n integrated TagPicker, and version history modal\n- Add shared MarkdownEditor component\n- Add Early-Stage Growth System (EGS): SpotlightEngine, FeedBlender,\n GridFiller, AdaptiveTimeWindow, ActivityLayer, admin panel\n- Fix upload category/tag persistence (V1+V2 paths)\n- Fix tag source enum, category tree display, binding resolution\n- Add settings.jsx Vite entry, settings.blade.php wrapper\n- Update ProfileController with JSON response support for API calls\n- Various route fixes (profile.edit, toolbar settings link)"
This commit is contained in:
78
app/Services/EarlyGrowth/AdaptiveTimeWindow.php
Normal file
78
app/Services/EarlyGrowth/AdaptiveTimeWindow.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\EarlyGrowth;
|
||||
|
||||
use App\Models\Artwork;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
/**
|
||||
* AdaptiveTimeWindow
|
||||
*
|
||||
* Dynamically widens the look-back window used by trending / rising feeds
|
||||
* when recent upload volume is below configured thresholds.
|
||||
*
|
||||
* This only affects RANKING QUERIES — it never modifies artwork timestamps,
|
||||
* canonical URLs, or any stored data.
|
||||
*
|
||||
* Behaviour:
|
||||
* uploads/day ≥ narrow_threshold → normal window (7 d)
|
||||
* uploads/day ≥ wide_threshold → medium window (30 d)
|
||||
* uploads/day < wide_threshold → wide window (90 d)
|
||||
*
|
||||
* All thresholds and window sizes are configurable in config/early_growth.php.
|
||||
*/
|
||||
final class AdaptiveTimeWindow
|
||||
{
|
||||
/**
|
||||
* Return the number of look-back days to use for trending / rising queries.
|
||||
*
|
||||
* @param int $defaultDays Returned as-is when EGS is disabled.
|
||||
*/
|
||||
public function getTrendingWindowDays(int $defaultDays = 30): int
|
||||
{
|
||||
if (! EarlyGrowth::adaptiveWindowEnabled()) {
|
||||
return $defaultDays;
|
||||
}
|
||||
|
||||
$uploadsPerDay = $this->getUploadsPerDay();
|
||||
$narrowThreshold = (int) config('early_growth.thresholds.uploads_per_day_narrow', 10);
|
||||
$wideThreshold = (int) config('early_growth.thresholds.uploads_per_day_wide', 3);
|
||||
|
||||
$narrowDays = (int) config('early_growth.thresholds.window_narrow_days', 7);
|
||||
$mediumDays = (int) config('early_growth.thresholds.window_medium_days', 30);
|
||||
$wideDays = (int) config('early_growth.thresholds.window_wide_days', 90);
|
||||
|
||||
if ($uploadsPerDay >= $narrowThreshold) {
|
||||
return $narrowDays; // Healthy activity → normal 7-day window
|
||||
}
|
||||
|
||||
if ($uploadsPerDay >= $wideThreshold) {
|
||||
return $mediumDays; // Moderate activity → expand to 30 days
|
||||
}
|
||||
|
||||
return $wideDays; // Low activity → expand to 90 days
|
||||
}
|
||||
|
||||
/**
|
||||
* Rolling 7-day average of approved public uploads per day.
|
||||
* Cached for `early_growth.cache_ttl.time_window` seconds.
|
||||
*/
|
||||
public function getUploadsPerDay(): float
|
||||
{
|
||||
$ttl = (int) config('early_growth.cache_ttl.time_window', 600);
|
||||
|
||||
return Cache::remember('egs.uploads_per_day', $ttl, function (): float {
|
||||
$count = Artwork::query()
|
||||
->where('is_public', true)
|
||||
->where('is_approved', true)
|
||||
->whereNull('deleted_at')
|
||||
->whereNotNull('published_at')
|
||||
->where('published_at', '>=', now()->subDays(7))
|
||||
->count();
|
||||
|
||||
return round($count / 7, 2);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user