'array', 'audio_seconds' => 'integer', 'audio_bytes' => 'integer', 'started_at' => 'datetime', 'completed_at' => 'datetime', ]; public function provider(): BelongsTo { return $this->belongsTo(AiVoiceProvider::class, 'provider_id'); } public static function latestFor(string $linkedid): ?self { return static::query() ->where('linkedid', $linkedid) ->orderByDesc('id') ->first(); } /** * A run that is still waiting or in flight — used to avoid queueing the * same call twice. */ public function scopeInFlight($query) { return $query->whereIn('status', [self::STATUS_QUEUED, self::STATUS_PROCESSING]); } public function isInFlight(): bool { return in_array($this->status, [self::STATUS_QUEUED, self::STATUS_PROCESSING], true); } public function isCompleted(): bool { return $this->status === self::STATUS_COMPLETED; } public function isFailed(): bool { return $this->status === self::STATUS_FAILED; } public function statusLabel(): string { return [ self::STATUS_QUEUED => 'Queued', self::STATUS_PROCESSING => 'Processing', self::STATUS_COMPLETED => 'Completed', self::STATUS_FAILED => 'Failed', ][$this->status] ?? ucfirst((string) $this->status); } }