Files
dockerank2/app/Console/Commands/SendScheduledInsights.php
T
2026-09-01 07:49:48 -03:00

196 lines
7.9 KiB
PHP
Executable File

<?php
declare(strict_types=1);
namespace App\Console\Commands;
use App\Enums\MessageType;
use App\Models\Insight;
use App\Models\TokenUsage;
use App\Models\UserSubject;
use App\Services\AIService;
use App\Services\MarkdownService;
use App\Services\TelegramService;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
/**
* Artisan command to send scheduled insights to users.
* Processes user subjects with active schedules and generates AI insights.
*/
class SendScheduledInsights extends Command
{
protected $signature = "insights:send
{--user= : Send insights for a specific user ID}
{--subject= : Send insights for a specific subject ID}
{--force : Ignore time and frequency checks, send immediately}
{--type= : Override message type (insight, language_lesson, game_simulator, technical_teaching)}";
protected $description = "Send scheduled insights to users based on their subject schedules";
public function handle(AIService $ai, TelegramService $telegram): int
{
$force = (bool) $this->option("force");
$overrideType = MessageType::tryFrom($this->option('type') ?? '') ?? null;
$query = UserSubject::where("is_active", true)
->whereHas("subject", function ($q) {
$q->where("is_active", true);
})
->with(["user", "subject"]);
if ($this->option("user")) {
$query->where("user_id", (int) $this->option("user"));
}
if ($this->option("subject")) {
$query->where("subject_id", (int) $this->option("subject"));
}
$subscriptions = $query->get();
$sentCount = 0;
foreach ($subscriptions as $subscription) {
if ($force || $this->shouldSend($subscription)) {
$this->sendInsight($subscription, $ai, $telegram, $overrideType);
$sentCount++;
}
}
$this->info("Sent {$sentCount} insights.");
return 0;
}
/**
* Determine if an insight should be sent based on the schedule.
* Only called when --force is NOT used.
*/
private function shouldSend(UserSubject $subscription): bool
{
$user = $subscription->user;
$timezone = $user->timezone ?? 'UTC';
$now = now()->setTimezone($timezone);
// Check if today is a scheduled weekday
if ($subscription->weekdays && is_array($subscription->weekdays) && count($subscription->weekdays) > 0) {
$today = (string) $now->format('w');
$found = false;
foreach ($subscription->weekdays as $day) {
if ((string) $day === $today) {
$found = true;
break;
}
}
if (!$found) {
return false; // Not a scheduled day
}
}
// Check if current hour:minute matches any preferred time
if ($subscription->preferred_times && is_array($subscription->preferred_times) && count($subscription->preferred_times) > 0) {
$currentHM = $now->format('H:i');
$matchFound = false;
foreach ($subscription->preferred_times as $time) {
$timeStr = is_string($time) ? substr($time, 0, 5) : '09:00';
// Allow 15-minute window each way (total 30 min window)
$scheduled = \Carbon\Carbon::parse($timeStr, $timezone);
$diff = abs($now->diffInMinutes($scheduled));
if ($diff <= 15) {
$matchFound = true;
break;
}
}
if (!$matchFound) {
return false; // Not the right time
}
}
// Never sent before - send now (time and weekday already match)
if (!$subscription->last_sent_at) {
return true;
}
// Check frequency
$lastSent = $subscription->last_sent_at->setTimezone($timezone);
return match ($subscription->frequency) {
"daily" => $lastSent->lt($now->copy()->subDay()),
"weekly" => $lastSent->lt($now->copy()->subWeek()),
"biweekly" => $lastSent->lt($now->copy()->subWeeks(2)),
"monthly" => $lastSent->lt($now->copy()->subMonth()),
default => true,
};
}
private function sendInsight(UserSubject $subscription, AIService $ai, TelegramService $telegram, ?MessageType $overrideType = null): void
{
$user = $subscription->user;
$subject = $subscription->subject;
$language = $subscription->insight_language ?? $user->locale ?? 'en';
$messageType = $overrideType ?? MessageType::AiGenerated;
$prompt = match ($messageType) {
MessageType::LanguageLesson => __('Teach me a practical language lesson about :subject with examples, practice sentences, pronunciation, grammar, and vocabulary.', ['subject' => $subject->name]),
MessageType::GameSimulator => __('Give me a strategic tip for :subject with actionable strategies, commands, and practical steps.', ['subject' => $subject->name]),
MessageType::TechnicalTeaching => __('Give me a technical teaching lesson about :subject covering components, techniques, repairs, schematics, and practical exercises.', ['subject' => $subject->name]),
default => __('Generate an insightful learning tip about :subject. Include practical advice and key concepts.', ['subject' => $subject->name]),
};
if ($subject->introduction) {
$prompt = __('Context about :subject: :introduction', [
'subject' => $subject->name,
'introduction' => $subject->introduction,
]) . "\n\n" . $prompt;
}
$result = $ai->generateInsight($subject->name, $prompt, ['language' => $language], $messageType);
if (!$result["success"]) {
$this->error("Failed to generate message for user {$user->id}: " . ($result["error"] ?? "Unknown error"));
return;
}
DB::transaction(function () use ($user, $subject, $result, $subscription, $telegram, $messageType) {
Insight::create([
"user_id" => $user->id,
"subject_id" => $subject->id,
"content" => $result["content"],
"type" => $messageType->value,
"input_tokens" => $result["input_tokens"],
"output_tokens" => $result["output_tokens"],
"sent_at" => now(),
]);
TokenUsage::create([
"user_id" => $user->id,
"input_tokens" => $result["input_tokens"],
"output_tokens" => $result["output_tokens"],
"total_tokens" => $result["input_tokens"] + $result["output_tokens"],
"model" => config("services." . config('services.ai.default_provider', 'openrouter') . ".model"),
"provider" => config('services.ai.default_provider', 'openrouter'),
]);
$subscription->update(["last_sent_at" => now()]);
// Send to Telegram if enabled
if ($subscription->hasChannel('telegram')) {
$telegramUser = $user->telegramUsers()
->where("is_active", true)
->first();
if ($telegramUser && $telegramUser->isFullyConfigured()) {
$botTelegram = new TelegramService($telegramUser->bot_token);
$plainText = MarkdownService::strip($result["content"]);
$botTelegram->sendMessage(
$telegramUser->chat_id,
"📚 *{$subject->name}*\n\n{$plainText}"
);
}
}
});
$this->info("Sent message to user {$user->id} for subject {$subject->id}");
}
}