Files
zyt/server/tests/DiagnosisAiRequiredSourcesContextTest.php
T
2026-10-27 14:03:35 +08:00

100 lines
6.7 KiB
PHP

<?php
declare(strict_types=1);
namespace think\facade {
class Cache {
public static array $entries = [];
public static function get(string $key) { return self::$entries[$key] ?? null; }
public static function set(string $key, $value, int $ttl): void { self::$entries[$key] = $value; }
}
class Log { public static function warning(string $message, array $context = []): void {} }
}
namespace app\adminapi\service {
function config(string $key): array { return ['context_compaction_cache_ttl' => 300]; }
}
namespace app\adminapi\logic\firstvisit {
class MyPatientLogic {
public static function canAccessDiagnosis($id, $adminId, $adminInfo): bool { ++$GLOBALS['requiredScopeChecks']; return true; }
}
}
namespace app\adminapi\logic\tcm {
function config(string $key): array { return ['timeout' => 5, 'models' => ['qwen' => ['name' => 'synthetic', 'label' => '合成模型']]]; }
class DiagnosisLogic {
public static function detail($params, $adminInfo): array { return ['id' => $params['id'], 'patient_id' => 99, 'gender' => '女', 'age' => 42]; }
}
class PatientAiReportLogic {
public static function contextForAuthorizedDiagnosis($diagnosis, $adminId, $adminInfo): array {
++$GLOBALS['requiredAggregates'];
return ['snapshot' => ['required_sections' => $GLOBALS['requiredRawSections'],
'synthetic_history' => str_repeat('合成无真实患者资料。', 9000) . $GLOBALS['requiredRevision']],
'source_summary' => ['required_sections' => $GLOBALS['requiredMetadata'], 'snapshot_complete' => true],
'source_diagnosis_ids' => [11, 12], 'files' => array_fill(0, 8,
['type' => 'document', 'url' => 'https://synthetic.invalid/required.pdf'])];
}
public static function compactSourceForPrompt(string $profile, string $source, array $options): array {
++$GLOBALS['requiredCompactions'];
foreach ($GLOBALS['requiredMetadata'] as $section) {
\contextRequiredExpect(str_contains($source, $section['label']), 'every required section enters the complete evidence extraction source');
}
if ($GLOBALS['requiredCancelInCompaction']) $GLOBALS['requiredContextCancelled'] = true;
return ['text' => '逐片合成证据摘要。', 'chunk_count' => 4, 'reduction_rounds' => 1,
'source_bytes' => strlen($source), 'compacted' => true];
}
}
}
namespace {
require dirname(__DIR__) . '/vendor/autoload.php';
use app\adminapi\logic\tcm\DiagnosisAiLogic;
use think\facade\Cache;
function contextRequiredExpect(bool $ok, string $message): void { if (!$ok) throw new RuntimeException($message); }
$requiredMetadata = []; $requiredRawSections = [];
foreach (['basic_info' => '基本信息', 'vital_signs' => '生命体征', 'chief_complaint' => '主诉', 'present_illness' => '现病史',
'past_history' => '既往史', 'other_history' => '其他病史', 'diagnosis_info' => '诊断信息', 'doctor_image_notes' => '医生备注图片',
'doctor_file_notes' => '医生备注文件', 'daily_tracking' => '日常整个页', 'prescriptions' => '历史处方',
'video_consultation_text' => '既往视频面诊转文字', 'chat' => '聊天'] as $key => $label) {
$empty = in_array($key, ['other_history', 'chat'], true);
$requiredMetadata[] = ['key' => $key, 'label' => $label, 'count' => $empty ? 0 : 1,
'availability' => $empty ? 'empty' : 'present', 'status' => 'prepared'];
$requiredRawSections[$key] = ['label' => $label, 'content' => $empty ? '无' : '合成记录'];
}
$requiredScopeChecks = 0; $requiredAggregates = 0; $requiredCompactions = 0; $requiredRevision = 'revision-1';
$requiredContextCancelled = false; $requiredCancelInCompaction = false;
$prepare = static function (): array|null {
$events = []; $started = null;
$result = DiagnosisAiLogic::prepareAssistant(11, 'summary', '', 7, ['root' => 1], null,
['should_abort' => static fn (): bool => $GLOBALS['requiredContextCancelled'],
'on_authorized' => static function (array $metadata) use (&$started): bool { $started = $metadata; return true; },
'on_progress' => static function (array $event) use (&$events): void { $events[] = $event; }]);
if ($result !== null) {
contextRequiredExpect($started['required_sections'] === [], 'start makes no section availability claims before aggregation');
contextRequiredExpect(count($events[1]['required_sections']) === 13 && count(end($events)['required_sections']) === 13,
'aggregation and prepared progress carry every mandatory section');
}
return $result;
};
$prepared = $prepare();
contextRequiredExpect(is_array($prepared) && $prepared['source_form'] === 'evidence_summary' && !$prepared['evidence_cache_hit'], 'large source is fully covered by evidence extraction');
foreach ($requiredMetadata as $section) {
contextRequiredExpect(str_contains($prepared['query'], $section['label']) && str_contains($prepared['inputs']['herbs_json'], $section['label']),
'required column survives compaction in both model input channels');
}
contextRequiredExpect(str_contains($prepared['query'], '其他病史:该栏目未提供记录(原标记:无)')
&& str_contains($prepared['query'], '聊天:该栏目未提供记录(原标记:无)'), 'empty required sections remain explicit no-data body entries, never clinical negatives');
contextRequiredExpect(count($prepared['required_sections']) === 13 && $prepared['required_sections'][0]['status'] === 'prepared', 'preparation never reports submission');
$cached = $prepare();
contextRequiredExpect($cached['evidence_cache_hit'] && $requiredCompactions === 1 && $requiredScopeChecks === 2 && $requiredAggregates === 2,
'cache reuse follows fresh scope authorization and complete live aggregation');
contextRequiredExpect($cached['required_sections'] === $prepared['required_sections']
&& str_contains($cached['query'], '聊天:该栏目未提供记录(原标记:无)')
&& count($cached['files']) === 8 && count($prepared['files']) === 8, 'cache cannot remove required source manifest or attachments');
$requiredRevision = 'revision-2'; $requiredCancelInCompaction = true;
contextRequiredExpect($prepare() === null && DiagnosisAiLogic::getAssistantErrorCode() === 'AI_ASSISTANT_CANCELLED'
&& count(Cache::$entries) === 1, 'cancelled evidence extraction is not cached or promoted to ready');
echo "Diagnosis AI required source prepare / compaction / cache / explicit empty context: OK\n";
}