90, 'models' => ['qwen' => ['name' => 'synthetic']]]; } class DiagnosisLogic { public static function detail($params, $adminInfo): array { return ['id' => 1, 'patient_id' => 7, 'gender' => '男', 'age' => 62]; } } class PatientAiReportLogic { public static function contextForAuthorizedDiagnosis($diagnosis, $adminId, $adminInfo): array { if (!empty($GLOBALS['slowAggregate'])) usleep(5000); return $GLOBALS['longitudinalFixture']; } public static function compactSourceForPrompt($profile, $source, $options): array { throw new \RuntimeException('A lossless below-threshold context must not invoke extraction'); } } } namespace { require dirname(__DIR__) . '/vendor/autoload.php'; use app\adminapi\logic\tcm\DiagnosisAiLogic; use app\adminapi\service\AiLosslessSourceEncoding as Encoding; use app\adminapi\service\AiRequiredSource; $checks = 0; function losslessExpect(bool $ok, string $message): void { ++$GLOBALS['checks']; if (!$ok) throw new RuntimeException($message); } $variants = [[], ['literal' => ['columns' => ['x'], 'rows' => [[1], [2]]]], ['rows' => [['id' => 1, 'text' => null], ['id' => 2, 'text' => '']], 'floats' => [1.0, 1.05, -0.0]], ['mixed' => [['id' => 1], ['text' => '未记录']], 'nested' => [ ['date' => '2026-01-01', 'herbs' => [['name' => '合成甲', 'dose' => 1], ['name' => '合成乙', 'dose' => 2]]], ['date' => '2026-02-01', 'herbs' => [['name' => '合成丙', 'dose' => 3], ['name' => '合成丁', 'dose' => 4]]]]], ['escaping' => [['x' => "\0|,\"\\无"], ['x' => '未记录']], 'empty' => [[], null, false, 0]]]; foreach ($variants as $fixture) losslessExpect(Encoding::decode(Encoding::encode($fixture)) === $fixture, 'Round trip preserves every value, key, null, order, and literal columns/rows object'); $daily = []; for ($i = 0; $i < 650; ++$i) $daily[] = ['id' => $i + 1, 'diagnosis_id' => 1, 'record_date' => '2026-01-01', 'record_time' => '07:00', 'record_time_estimated' => false, 'record_time_period' => 'fasting', 'blood_sugar' => '6.10', 'fasting_blood_sugar' => null, 'postprandial_blood_sugar' => '', 'systolic_pressure' => 120, 'diastolic_pressure' => 80, 'western_medicine' => '合成用药原文-' . $i, 'insulin' => null, 'remark' => '未记录-' . $i]; $sections = []; foreach (AiRequiredSource::LABELS as $key => $label) $sections[] = ['key' => $key, 'label' => $label, 'value' => $key === 'daily_tracking' ? ['blood_glucose_pressure' => $daily, 'diet' => [], 'exercise' => []] : ($key === 'chat' ? ['tencent_im' => [['id' => 1, 'text' => '最后一条原文及无明显限定词']]] : '无')]; $snapshot = ['snapshot_version' => 'synthetic', 'required_content' => $sections]; $metadata = array_map(static fn($s) => ['key' => $s['key'], 'label' => $s['label'], 'count' => $s['key'] === 'daily_tracking' ? 650 : ($s['key'] === 'chat' ? 1 : 0), 'availability' => in_array($s['key'], ['daily_tracking', 'chat'], true) ? 'present' : 'empty'], $sections); $longitudinalFixture = ['snapshot' => $snapshot, 'source_summary' => ['required_sections' => $metadata, 'snapshot_complete' => true, 'may_be_truncated' => false], 'source_diagnosis_ids' => [1], 'files' => [['type' => 'image', 'url' => 'https://example.invalid/synthetic.png']]]; $context = (new ReflectionMethod(DiagnosisAiLogic::class, 'buildCaseContext'))->invoke(null, ['id' => 1, 'gender' => '男', 'age' => 62], 7, ['root' => 1]); $raw = json_encode($snapshot, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); losslessExpect(strlen($raw) > 120000 && strlen($context['case_text']) < 120000, 'Repetitive full history crosses old threshold but fits losslessly'); $firstLine = $context['case_lines'][0]; $wire = substr($firstLine, strpos($firstLine, '{')); losslessExpect(Encoding::decode($wire) === $snapshot, 'Actual prompt contains the full snapshot in reversible form'); $payload = json_decode($context['case_json'], true); losslessExpect($payload['case_lines'] === $context['case_lines'], 'Structured and query channels use the same complete wire source'); losslessExpect($context['files'] === $longitudinalFixture['files'] && count($context['required_sections']) === 13, 'Attachments and all thirteen required categories remain intact'); $originalLines = $context['case_lines']; $originalLines[0] = '患者纵向完整资料(服务端实时聚合,JSON):' . $raw; $originalPayload = ['gender' => '男', 'age' => '62', 'case_lines' => $originalLines, 'source_diagnosis_ids' => [1], 'source_summary' => $context['source_summary'], 'file_count' => 1]; losslessExpect($context['fingerprint'] === hash('sha256', json_encode($originalPayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE)), 'Original source fingerprint meaning is unchanged'); $before = $context; losslessExpect((new ReflectionMethod(DiagnosisAiLogic::class, 'fitContextForPrompt'))->invokeArgs(null, [&$context, 'qwen']) && $context === $before, 'Below-threshold packed source starts no model extraction and stays full-context'); $slowAggregate = true; $timeouts = 0; $result = DiagnosisAiLogic::prepareAssistant(1, 'summary', '', 7, ['root' => 1], null, ['timeout_ms' => 1, 'on_timeout' => static function () use (&$timeouts): void { ++$timeouts; }]); losslessExpect($result === null && DiagnosisAiLogic::getAssistantErrorCode() === 'UPSTREAM_TIMEOUT' && $timeouts === 1, 'Aggregation cannot silently restart the answer budget after the deadline'); $calls = 0; losslessExpect(DiagnosisAiLogic::streamPreparedAssistant(['request_deadline_ns' => hrtime(true) - 1], static function () use (&$calls): void { ++$calls; }) === null && $calls === 0 && DiagnosisAiLogic::getAssistantErrorCode() === 'UPSTREAM_TIMEOUT', 'Expired prepared request never invokes final transport'); losslessExpect(DiagnosisAiLogic::streamPreparedAssistant(['request_deadline_ns' => hrtime(true) - 1], static function (): void {}, null, null, ['on_timeout' => static function (): void { throw new RuntimeException('observer failure'); }]) === null && DiagnosisAiLogic::getAssistantErrorCode() === 'UPSTREAM_TIMEOUT', 'Observer failure cannot replace the terminal timeout classification'); echo "Lossless full-history context and shared preparation deadline: {$checks} checks OK\n"; }