feat: add reviewed follow-up audio patient enrichment
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\logic\tcm;
|
||||
|
||||
use app\common\service\followupaudio\FollowupAudioAccess as Access;
|
||||
use app\common\service\followupaudio\FollowupAudioApply as Apply;
|
||||
use app\common\service\followupaudio\FollowupAudioFields as Fields;
|
||||
use app\common\service\followupaudio\FollowupAudioStore as Store;
|
||||
use app\common\service\followupaudio\FollowupAudioUpload as Upload;
|
||||
use DomainException;
|
||||
|
||||
final class FollowupAudioLogic
|
||||
{
|
||||
public static function capabilities(int $diagnosisId, int $actor, array $info): array
|
||||
{
|
||||
$diagnosis = Access::diagnosis($diagnosisId, $actor, $info);
|
||||
$enabled = Store::enabled();
|
||||
$verified = Store::verified();
|
||||
$daily = Access::canDaily($actor, $info);
|
||||
return [
|
||||
'enabled' => $enabled, 'audio_verified' => $verified,
|
||||
'can_upload' => $enabled && $verified, 'can_apply' => $enabled && $verified,
|
||||
'can_daily' => $daily, 'limits' => Upload::limits(),
|
||||
'models' => array_values(array_filter(
|
||||
[['value' => 'qwen', 'label' => '千问'], ['value' => 'openai', 'label' => 'OpenAI']],
|
||||
static fn (array $model): bool => Store::verified($model['value'])
|
||||
)),
|
||||
// No migration/dictionary dependency is introduced when the feature is OFF.
|
||||
'fields' => $enabled ? self::catalogForActor($diagnosis, $actor, $info) : [],
|
||||
];
|
||||
}
|
||||
|
||||
public static function requireEnabled(bool $verified = false): void
|
||||
{
|
||||
if (!Store::enabled()) {
|
||||
throw new DomainException('回访录音功能尚未启用');
|
||||
}
|
||||
if ($verified && !Store::verified()) {
|
||||
throw new DomainException('Dify 音频能力尚未验证,暂不接受真实录音或写入');
|
||||
}
|
||||
}
|
||||
|
||||
public static function create(array $p, int $actor, array $info): array
|
||||
{
|
||||
self::requireEnabled(true);
|
||||
Access::diagnosis($p['diagnosis_id'], $actor, $info);
|
||||
$upload = Upload::owned($p['upload_id'], $actor, $info);
|
||||
if ((int) $upload['diagnosis_id'] !== $p['diagnosis_id'] || $upload['status'] !== 'complete') {
|
||||
throw new DomainException('录音与当前诊单不匹配或尚未上传完成');
|
||||
}
|
||||
Upload::path($upload);
|
||||
$date = \DateTimeImmutable::createFromFormat('!Y-m-d H:i:s', $p['recorded_at'], new \DateTimeZone('Asia/Shanghai'));
|
||||
if (!$date || $date->format('Y-m-d H:i:s') !== $p['recorded_at'] || $date->getTimestamp() > time() + 300) {
|
||||
throw new DomainException('请填写正确的实际通话时间(北京时间),不能晚于当前时间');
|
||||
}
|
||||
if (!in_array($p['model_key'], ['qwen', 'openai'], true) || !Store::verified($p['model_key'])) {
|
||||
throw new DomainException('所选模型音频能力尚未验证');
|
||||
}
|
||||
return Store::create($upload, $p['recorded_at'], $p['model_key'], $actor, $info);
|
||||
}
|
||||
|
||||
public static function lists(int $diagnosisId, int $actor, array $info): array
|
||||
{
|
||||
Access::diagnosis($diagnosisId, $actor, $info);
|
||||
return ['items' => Store::enabled() ? Store::lists($diagnosisId) : []];
|
||||
}
|
||||
|
||||
public static function detail(int $id, int $actor, array $info): array
|
||||
{
|
||||
Access::task($id, $actor, $info);
|
||||
return self::protectDetail(Store::detail($id), $actor, $info);
|
||||
}
|
||||
|
||||
public static function saveDraft(int $id, int $version, array $items, int $actor, array $info): array
|
||||
{
|
||||
self::requireEnabled();
|
||||
$task = Access::task($id, $actor, $info);
|
||||
self::checkDailyItems($task, $items, $actor, $info);
|
||||
return self::protectDetail(Store::saveDraft($id, $version, $items, $actor), $actor, $info);
|
||||
}
|
||||
|
||||
public static function apply(int $id, int $version, array $items, int $actor, array $info): array
|
||||
{
|
||||
self::requireEnabled(true);
|
||||
$task = Access::task($id, $actor, $info);
|
||||
self::checkDailyItems($task, $items, $actor, $info);
|
||||
return Apply::apply($id, $version, $items, $actor, $info);
|
||||
}
|
||||
|
||||
public static function retry(int $id, int $actor, array $info): array
|
||||
{
|
||||
self::requireEnabled(true);
|
||||
Access::task($id, $actor, $info);
|
||||
return Store::retry($id);
|
||||
}
|
||||
|
||||
private static function checkDailyItems(array $task, array $items, int $actor, array $info): void
|
||||
{
|
||||
$diagnosis = Access::diagnosis((int) $task['diagnosis_id'], $actor, $info);
|
||||
$plain = Access::allowed($actor, Access::actor($actor), 'tcm.diagnosis/phonePlain');
|
||||
foreach ($items as $item) {
|
||||
if (!is_array($item)) {
|
||||
throw new DomainException('审阅内容无效');
|
||||
}
|
||||
if (!empty($item['selected']) && ($item['kind'] ?? '') === 'diagnosis' && !$plain) {
|
||||
foreach (['phone', 'id_card'] as $key) {
|
||||
if ($key === 'id_card' && trim((string) ($diagnosis[$key] ?? '')) === '') {
|
||||
continue;
|
||||
}
|
||||
if (array_key_exists($key, (array) ($item['values'] ?? []))
|
||||
&& (string) $item['values'][$key] !== (string) ($diagnosis[$key] ?? '')) {
|
||||
throw new DomainException('无权通过录音修改已有手机号或身份证');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($item['selected']) && ($item['kind'] ?? '') !== 'diagnosis') {
|
||||
Access::diagnosis((int) $task['diagnosis_id'], $actor, $info, true);
|
||||
// Continue to validate identity suggestions later in this same batch.
|
||||
}
|
||||
}
|
||||
}
|
||||
private static function catalogForActor(array $diagnosis, int $actor, array $info): array
|
||||
{
|
||||
$info = Access::actor($actor);
|
||||
$catalog = Fields::catalog();
|
||||
$basic = DiagnosisLogic::canEditPatientBasicInfo((int) $diagnosis['id'], $info);
|
||||
$plain = Access::allowed($actor, $info, 'tcm.diagnosis/phonePlain');
|
||||
foreach ($catalog['diagnosis'] as &$field) {
|
||||
$key = $field['key'];
|
||||
if ((!$basic && in_array($key, ['patient_name', 'id_card', 'phone', 'gender', 'age'], true))
|
||||
|| (!$plain && ($key === 'phone' || ($key === 'id_card' && !empty($diagnosis['id_card']))))) {
|
||||
$field['readonly'] = true;
|
||||
}
|
||||
}
|
||||
unset($field);
|
||||
return $catalog;
|
||||
}
|
||||
|
||||
private static function protectDetail(array $detail, int $actor, array $info): array
|
||||
{
|
||||
$diagnosis = Access::diagnosis((int) $detail['diagnosis_id'], $actor, $info);
|
||||
$detail['fields'] = self::catalogForActor($diagnosis, $actor, $info);
|
||||
if (!Access::allowed($actor, Access::actor($actor), 'tcm.diagnosis/phonePlain')) {
|
||||
foreach ($detail['items'] as &$item) {
|
||||
foreach (['phone', 'id_card'] as $key) {
|
||||
$value = (string) ($item['current_values'][$key] ?? '');
|
||||
if ($value !== '') {
|
||||
$item['current_values'][$key] = mb_substr($value, 0, 3) . '****' . mb_substr($value, -4);
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($item);
|
||||
}
|
||||
return $detail;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user