invoke(null, CompleteTcmCase::rows()); $submission = (new ReflectionMethod(PatientAiReportLogic::class, 'sanitizeSnapshotForUpstream'))->invoke(null, AiRequiredSource::submission($raw)); $ledger = Guard::build(CompleteTcmCase::current(), $submission); $ledgerBytes = serialize($ledger); $expect($ledgerBytes === serialize($beforeGuard::build(CompleteTcmCase::current(), $submission)), 'Before/current construction byte-identical.'); $expect(hash('sha256', json_encode($ledger, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)) === '2ebbec0b558347bf4ee128dfb5a9e63fc4e355d58f7226486a2ae96098bd57f6', 'Actual complete ledger pinned.'); $index = Index::build($ledger); $head = "一、四诊\n\n"; foreach (Index::ROLES as $label => $role) { $found = null; foreach ($index['sources'] as $source) { if ($source['scope'] !== 'current') continue; foreach ($source['spans'] as $span) if ($span['role'] === $role && $span['recorded']) { $found = [$source, $span]; break 2; } } if ($found === null) $head .= $label . ($role === 'palpation' ? ":其他触诊未记录,脉象见独立脉象项。\n\n" : ":未记录\n\n"); else { [$source, $span] = $found; $head .= $label . ':' . $source['date'] . '“' . trim($span['quote']) . '”(' . $source['id'] . ")。\n\n"; } } $head .= "二、八纲\n\n"; $expect(Guard::validate($ledger, $head)['valid'] && Contract::validate($index, $head, true)['valid'], 'Real six-role source-quoted prefix valid.'); $check = static function (string $unit, bool $valid, bool $oldBypass = false) use ( $expect, $ledger, $index, $head, $beforeGuard, &$cases): void { ++$cases; $original = $unit; $guard = Guard::validate($ledger, $head . $unit); $contract = Contract::validate($index, $head . $unit, true); $expect($guard['valid'] === $valid, 'Guard ' . $unit . ': ' . json_encode($guard)); $expect(($guard['valid'] && $contract['valid']) === $valid, 'Composed result ' . $unit . ': ' . json_encode($contract)); if (!$valid) $expect(in_array('SOURCE_ANNOTATION_INVALID', array_column($guard['issues'], 'code'), true), 'Every local operand must have its own actual source ' . $unit); if ($oldBypass) $expect($beforeGuard::validate($ledger, $head . $unit)['valid'], 'Unchanged exact baseline bypass reproduced.'); foreach ([1, 13, 47] as $size) { $visible = ''; $stream = new Stream($ledger, static function (string $delta) use (&$visible): bool { $visible .= $delta; return true; }, null, $head, null, $index); foreach (mb_str_split($unit . "\n\n", $size, 'UTF-8') as $chunk) if (!$stream->push($chunk)) break; $expect($stream->finish() === $valid && $stream->rejected() === !$valid, 'Stream result ' . $size . ': ' . $unit); $expect($visible === ($valid ? $unit . "\n\n" : ''), 'Exact raw accepted bytes / no rejected unit release ' . $size); } $expect($unit === $original, 'Raw model unit unchanged.'); }; foreach (['且', '并且', '而且', '并', '而', '亦', '也'] as $join) { $check('脉细弱(s10),当前无力' . $join . '滑(s10)。', false, $join === '且'); $check('脉细弱(s10),当前无力' . $join . '有力(s10)。', false, $join === '且'); $check('脉细弱(s10),当前细' . $join . '无力(s10)。', true); $check('脉细弱(s10),当前不浮' . $join . '不滑(s10)。', true); $check('脉细弱(s10),当前不浮' . $join . '无力(s10)。', true); $check('脉细弱(s10),当前不浮' . $join . '滑(s10)。', false); $check('脉细弱(s10),当前不细' . $join . '不弱(s10)。', false); $check('脉细弱' . $join . '滑(s10)。', false); $check('脉细弱' . $join . '无力(s10)。', true); $check('舌质淡红(s9),当前淡红' . $join . '少津(s9)。', true); $check('舌质淡红(s9),当前淡红' . $join . '胖大(s9)。', false); $check('脉细弱(s10),当前' . $join . '无力(s3)。', false); $check('脉细弱(s10),当前' . $join . '无力(s10)。', true); } foreach (['脉细弱(s10),当前无力并且按之滑(s10)。', '舌质淡红、脉细弱且滑(s9、s10)。', '脉细弱且滑、舌质淡红(s9、s10)。', '脉细弱(s10),当前无力且细(s3)。', '脉细弱(s10),当前无力且细(2020-05-02,s10)。', '脉细弱(s10),既往无力且细(s10)。', '脉细弱(s10),当前无力且细(s14)。'] as $unit) $check($unit, false); foreach (['舌质淡红且少津(s9)。', '舌质淡红、脉细弱且无力(s9、s10)。', '脉细弱且无力、舌质淡红(s9、s10)。', '脉细弱(s10),当前无力并且按之无力(s10)。', '脉细弱(s10),当前细且弱并且无力(s1、s2、s10)。'] as $unit) $check($unit, true); $check('脉细弱(s10),当前无力且' . str_repeat('细', 50) . '滑(s10)。', false); $check('脉细弱(s10),当前' . str_repeat('细', 50) . '且无力(s10)。', true); $check('脉细弱(s10),当前不浮且不' . str_repeat('滑', 50) . '细(s10)。', false); $check('脉细弱(s10),当前不浮且不' . str_repeat('滑', 50) . '(s10)。', true); // Ordinary subjects and real terminators retain complete parser-array // equality to d. These are reference reset diagnostics, not clinical passes. $parse = new ReflectionMethod(Guard::class, 'reportClauses'); $oldParse = new ReflectionMethod($beforeGuard, 'reportClauses'); foreach (['且', '并且', '而且', '并', '而', '但', '亦', '也'] as $join) { foreach (['口干', '腹部按之无力'] as $subject) { $unit = '脉细弱(s10),当前无力' . $join . $subject . '(s3),目前无力(s3)。'; $expect($parse->invoke(null, $ledger, $unit) === $oldParse->invoke(null, $ledger, $unit), 'Ordinary conjunction subject resets: ' . $unit); } } foreach (['。', ';', ';', '!', '?', "\n"] as $end) { $unit = '脉细弱(s10)' . $end . '当前无力且细(s3)。'; $expect($parse->invoke(null, $ledger, $unit) === $oldParse->invoke(null, $ledger, $unit), 'Real boundary still clears inherited exam kind.'); } foreach ([['“','”'], ['「','」'], ['"','"']] as [$open, $close]) { $unit = '切诊:2026-10-23' . $open . $ledger['sources']['s10']['quote'] . $close . '(s10),当前无力且细(s3)。'; $expect($parse->invoke(null, $ledger, $unit) === $oldParse->invoke(null, $ledger, $unit), 'Full source-quote boundary unchanged.'); } $finite = new ReflectionMethod(Guard::class, 'finiteExamConjunctionProse'); $preserved = $parse->invoke(null, $ledger, '脉细弱(s10),当前不浮滑且不紧(s10)。'); $negativeTopics = []; foreach ($preserved as $entry) foreach ($entry['exam_conjunction_parts'] ?? [] as $part) { if ($part['state'] === 'negative') $negativeTopics[] = $part['topic']; } $expect(in_array('脉浮滑', $negativeTopics, true) && !in_array('脉浮', $negativeTopics, true) && !in_array('脉滑', $negativeTopics, true), 'A compound negative operand remains one original polarity scope.'); foreach (['无力且口干', '无力且腹部按之无力', '无力并且患者无固定刺痛', '无力而且目前口干', '无力且且滑', '且滑', '无力且', '按之无力且腹部按之滑'] as $notClosed) { $expect($finite->invoke(null, 'pulse', $notClosed) === null, 'No ordinary/malformed prose is normalized: ' . $notClosed); } $expect($parse->invoke(null, $ledger, $head) === $oldParse->invoke(null, $ledger, $head), 'Whole actual quoted six-role prefix unchanged.'); $expect(serialize($ledger) === $ledgerBytes, 'All source/ledger bytes unchanged.'); $expect(!class_exists('app\\adminapi\\logic\\tcm\\DiagnosisAiLogic', false), 'Inference Logic not loaded.'); echo json_encode(['scope' => 'r25e finite same-exam conjunction/source/polarity integration', 'assertions' => $checks, 'cases' => $cases, 'failures' => $failures], JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR), "\n"; exit($failures === [] ? 0 : 1);