Files
lgp-admin-plus-api/app/Service/common/ai/AiDisclaimerService.php

198 lines
7.0 KiB
PHP
Raw Normal View History

2026-08-10 15:51:00 +08:00
<?php
namespace App\Service\common\ai;
use App\BaseApp\BaseService;
use App\Models\new\ai\AiDoctorConsentModel;
use App\Models\new\ai\AiFeatureCopyModel;
use App\Models\new\vip\VipFeatureModel;
/**
* AI 醒目提示文案 + 首次知情同意
* 文案按 VIP 功能码隔离;医生签署后仅该功能无需再次确认
*/
class AiDisclaimerService extends BaseService
{
public const FEATURE_MR = 'ai_medical_record';
public const FEATURE_RX = 'ai_prescription';
/**
* 系统配置:启用中的文案列表(含 VIP 功能名)
*/
public function listCopiesForConfig(): array
{
$rows = AiFeatureCopyModel::where('deleted_at', 0)
->orderByDesc('sort')
->orderBy('id')
->get()
->toArray();
$nameMap = VipFeatureModel::where('deleted_at', 0)
->whereIn('code', array_column($rows, 'feature_code'))
->get(['code', 'name'])
->keyBy('code')
->toArray();
foreach ($rows as &$row) {
$code = (string) ($row['feature_code'] ?? '');
$row['feature_name'] = $nameMap[$code]['name'] ?? $code;
$row['disclaimer_text'] = (string) ($row['disclaimer_text'] ?? '');
$row['consent_text'] = (string) ($row['consent_text'] ?? '');
}
unset($row);
return $rows;
}
/**
* 批量保存文案(系统配置 AI 模型 Tab
*
* @param list<array{id?:int,feature_code?:string,record_label?:string,disclaimer_text?:string,consent_text?:string}> $items
*/
public function saveCopies(array $items): bool
{
if (empty($items)) {
$this->utils->errorThrow('无保存内容');
}
$now = time();
foreach ($items as $item) {
if (!is_array($item)) {
continue;
}
$id = (int) ($item['id'] ?? 0);
$code = trim((string) ($item['feature_code'] ?? ''));
$row = null;
if ($id > 0) {
$row = AiFeatureCopyModel::where('id', $id)->where('deleted_at', 0)->first();
}
if (!$row && $code !== '') {
$row = AiFeatureCopyModel::where('feature_code', $code)->where('deleted_at', 0)->first();
}
if (!$row) {
continue;
}
if (array_key_exists('record_label', $item)) {
$row->record_label = trim((string) $item['record_label']);
}
if (array_key_exists('disclaimer_text', $item)) {
$row->disclaimer_text = (string) $item['disclaimer_text'];
}
if (array_key_exists('consent_text', $item)) {
$row->consent_text = (string) $item['consent_text'];
}
$row->updated_at = $now;
$row->save();
}
return true;
}
/**
* 按功能码取文案;不存在则回落内置默认
*/
public function getCopyByFeature(string $featureCode): array
{
$featureCode = trim($featureCode);
if ($featureCode === '') {
$this->utils->errorThrow('功能码不能为空');
}
$row = AiFeatureCopyModel::where('feature_code', $featureCode)
->where('deleted_at', 0)
->where('status', 1)
->first();
if ($row) {
return [
'id' => (int) $row->id,
'feature_code' => (string) $row->feature_code,
'record_label' => (string) ($row->record_label ?: $this->defaultRecordLabel($featureCode)),
'disclaimer_text' => (string) ($row->disclaimer_text ?: $this->defaultDisclaimer($featureCode)),
'consent_text' => (string) ($row->consent_text ?: $this->defaultConsent()),
];
}
return [
'id' => 0,
'feature_code' => $featureCode,
'record_label' => $this->defaultRecordLabel($featureCode),
'disclaimer_text' => $this->defaultDisclaimer($featureCode),
'consent_text' => $this->defaultConsent(),
];
}
/**
* 门闸:文案 + 是否已签署(按医生+功能码)
*/
public function getGate(string $featureCode, int $doctorId): array
{
if ($doctorId <= 0) {
$this->utils->errorThrow('无法识别医生身份');
}
$copy = $this->getCopyByFeature($featureCode);
$consented = AiDoctorConsentModel::where('doctor_id', $doctorId)
->where('feature_code', $featureCode)
->where('deleted_at', 0)
->exists();
return array_merge($copy, [
'consented' => $consented,
]);
}
/**
* 医生签署知情同意(同一功能幂等)
*/
public function agree(string $featureCode, int $doctorId, int $storeId = 0): array
{
if ($doctorId <= 0) {
$this->utils->errorThrow('无法识别医生身份');
}
$featureCode = trim($featureCode);
if ($featureCode === '') {
$this->utils->errorThrow('功能码不能为空');
}
$copy = $this->getCopyByFeature($featureCode);
$exists = AiDoctorConsentModel::where('doctor_id', $doctorId)
->where('feature_code', $featureCode)
->where('deleted_at', 0)
->first();
if ($exists) {
return [
'consented' => true,
'feature_code' => $featureCode,
'already' => true,
];
}
$now = time();
AiDoctorConsentModel::query()->insert([
'doctor_id' => $doctorId,
'feature_code' => $featureCode,
'copy_id' => (int) ($copy['id'] ?? 0),
'store_id' => $storeId,
'created_at' => $now,
'updated_at' => 0,
'deleted_at' => 0,
]);
return [
'consented' => true,
'feature_code' => $featureCode,
'already' => false,
];
}
private function defaultRecordLabel(string $featureCode): string
{
return $featureCode === self::FEATURE_RX ? '处方' : '病历';
}
private function defaultDisclaimer(string $featureCode): string
{
$label = $this->defaultRecordLabel($featureCode);
return '本' . $label . '由 AI 根据问诊/语音转写内容辅助生成,仅供起草参考。'
. "\nAI 可能存在漏记、误读或“幻觉”,不能替代医师专业判断。"
. "\n诊断、处置、用药、知情告知等关键内容须由你逐条核对、修改后确认。";
}
private function defaultConsent(): string
{
return "我已知悉:\n"
. "1. 本平台 AI 仅用于病历起草、信息归纳、术语规范化;\n"
. "2. 不得用于自动诊断、自动开嘱、自动出报告;\n"
. "3. 所有 AI 输出须由具备资质的医师审核、修改、电子签名;\n"
. "4. 因未审核直接使用 AI 内容导致的医疗差错,由操作医师及医疗机构承担责任。";
}
}