Files
zyt/server/app/common/service/followupaudio/FollowupAudioGate.php
T

70 lines
3.2 KiB
PHP

<?php
declare(strict_types=1);
namespace app\common\service\followupaudio;
use DomainException;
/** Preview is a separate, ID-scoped recognition permission; never permission to write clinical facts. */
final class FollowupAudioGate
{
public static function previewOnly(?array $settings = null): bool
{
return (bool) (($settings ?? (array) config('followup_audio', []))['preview_only'] ?? false);
}
public static function scopeAllowed(int $diagnosisId, int $actor, ?array $settings = null): bool
{
$settings = $settings ?? (array) config('followup_audio', []);
if (!self::previewOnly($settings)) { return true; }
try { $diagnoses = self::ids($settings['test_diagnosis_ids'] ?? ''); $admins = self::ids($settings['test_admin_ids'] ?? ''); }
catch (DomainException $error) { return false; }
return $diagnosisId > 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'); }
}
}