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"; }