0 && $actor > 0 && $diagnoses !== [] && in_array($diagnosisId, $diagnoses, true) && ($admins === [] || in_array($actor, $admins, true)); } private static function ids($raw): array { if (is_string($raw)) { $raw = trim($raw) === '' ? [] : explode(',', $raw); } if (!is_array($raw) || !array_is_list($raw) || count($raw) > 1000) { throw new DomainException('FOLLOWUP_AUDIO_PREVIEW_SCOPE_INVALID'); } $ids = []; foreach ($raw as $entry) { if ((!is_int($entry) && !is_string($entry)) || !preg_match('/^[1-9][0-9]{0,17}$/D', trim((string) $entry))) { throw new DomainException('FOLLOWUP_AUDIO_PREVIEW_SCOPE_INVALID'); } $ids[] = (int) trim((string) $entry); } return array_values(array_unique($ids)); } public static function assertScope(int $diagnosisId, int $actor, ?array $settings = null): void { if (!self::scopeAllowed($diagnosisId, $actor, $settings)) { throw new DomainException('FOLLOWUP_AUDIO_PREVIEW_SCOPE_DENIED'); } } public static function ready(?string $profile = null, ?array $settings = null, ?array $legacy = null): bool { $settings = $settings ?? (array) config('followup_audio', []); $legacy = $legacy ?? (array) config('prescription_ai', []); if ($profile === null) { foreach (['qwen', 'openai'] as $slot) { if (self::ready($slot, $settings, $legacy)) { return true; } } return false; } return self::previewOnly($settings) ? FollowupAudioProviderConfig::previewVerified($profile, $settings, $legacy) : FollowupAudioProviderConfig::verified($profile, $settings, $legacy); } /** Persisted preview-origin tasks never become eligible for adoption after a configuration change. */ public static function taskPreview(array $task): bool { $ids = json_decode((string) ($task['upstream_ids_json'] ?? '{}'), true); if (!is_array($ids) || !array_key_exists('preview_only', $ids)) { return false; } if ($ids['preview_only'] !== true) { throw new DomainException('FOLLOWUP_AUDIO_PREVIEW_MARKER_INVALID'); } return true; } public static function assertMayApply(?array $task = null): void { if (self::previewOnly() || ($task !== null && self::taskPreview($task))) { throw new DomainException('FOLLOWUP_AUDIO_PREVIEW_ONLY'); } } }