70 lines
3.2 KiB
PHP
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'); }
|
|
}
|
|
}
|