')); foreach ($cases as $case) { $ledger = ['sources' => [], 'assertions' => []]; foreach ($case as [$id, $field, $scope, $date, $quote]) $ledger['sources'][$id] = [ 'id' => $id, 'field' => $field, 'path' => $scope . '.' . $field . '.' . $id, 'scope' => $scope, 'date' => $date, 'quote' => $quote]; $originalLedger = serialize($ledger); $index = Index::build($ledger); $originalIndex = serialize($index); $prompt = Index::prompt($index); priorityScopeExpect(serialize($ledger) === $originalLedger && serialize($index) === $originalIndex, 'Qualitative/quantitative findings and baseline laboratory source payloads are never rewritten by policy generation.'); preg_match('~\s*(.*?)\s*~su', $prompt, $match); $rows = json_decode($match[1], true, 64, JSON_THROW_ON_ERROR); priorityScopeExpect(count($rows) === count($case), 'No source disappears when the instructions distinguish known and missing portions.'); foreach ($rows as $row) { $source = $ledger['sources'][$row['id']]; priorityScopeExpect($row['field'] === $source['field'] && $row['path'] === $source['path'] && $row['scope'] === $source['scope'] && $row['date'] === $source['date'] && implode('', array_column($row['spans'], 'quote')) === $source['quote'], 'Exact activity qualifiers, per-occasion/total values, negative qualifiers and embedded lab dates survive unchanged.'); } $policy = substr($prompt, 0, strpos($prompt, '')); foreach (['每个主要治法限定语及既定优先顺序', '其实际功用怎样支持', '不能只声称‘一致’或‘覆盖’', '不把作用相近的不同治法视为同义', '不能为迁就方名倒改第五节', '任一主要限定或优先目标无法解释时,不列方名药味,说明缺证点及所需补采', '定性程度、活动或诱发关系、饮水方式及既有检验项目和原日期', '缺量化值须明确每次、全天或具体时段,缺检查须明确检验种类', '不能因定量或特定检验缺失,把已有程度、摄入方式或实验室基线整体改成未知'] as $rule) { priorityScopeExpect(str_contains($policy, $rule), 'Both differing source cases receive the same generic explanation/scope requirement: ' . $rule); } priorityScopeExpect(str_contains($policy, '完整句或分号分隔的完整片段') && str_contains($policy, '保留时间、询问或记录语境、已知及程度等限定词,不能只摘否认核心') && str_contains($policy, '原有全部资料仍须阅读') && str_contains($policy, '索引不包含新的诊断规则'), 'Strict canonical quotation, complete original-source reading and the no-new-diagnostic-rule boundary remain intact.'); priorityScopeExpect($policy === $sourceIndependentPolicy, 'The reasoning policy is identical for unrelated source payloads and supplies no case-dependent selection or answer prefill.'); } $pulseSource = ['id' => 's7', 'field' => 'pulse', 'path' => 'current.pulse', 'scope' => 'current', 'date' => '2026-03-04', 'quote' => '脉缓而有力。']; $palpationSource = ['id' => 's8', 'field' => 'content', 'path' => 'current.doctor_note.content', 'scope' => 'current', 'date' => '2026-03-04', 'quote' => '切诊前臂触温偏凉。']; $historicalPalpation = array_replace($palpationSource, ['path' => 'historical.doctor_note.content', 'scope' => 'historical', 'date' => '2025-04-02']); foreach ([[$pulseSource], [$pulseSource, $palpationSource], [$pulseSource, $historicalPalpation]] as $caseNumber => $sources) { $ledger = ['sources' => array_column($sources, null, 'id')]; $before = serialize($ledger); $index = Index::build($ledger); $prompt = Index::prompt($index); $policy = substr($prompt, 0, strpos($prompt, '')); foreach (['望诊', '闻诊', '问诊', '切诊', '舌象', '脉象'] as $label) priorityScopeExpect(str_contains($policy, '“' . $label . ':”'), 'Every exact mandatory prefix is specified even when its source role has no observation.'); priorityScopeExpect(str_contains($policy, '每项独立段落且不得省略;六项齐全后才能开始第二节八纲') && str_contains($policy, '独立脉象段不能代替切诊段'), 'The second-section boundary requires all six independent paragraphs; pulse never substitutes for palpation.'); priorityScopeExpect(str_contains($policy, '仅当本次确无其他已记录切诊内容时,仍须独立写“切诊:其他触诊未记录,脉象见独立脉象项。”') && str_contains($policy, '有其他本次切诊记录则按原日期、原文与sN引用,不得改成未记录'), 'The closed missing-palpation metadata is conditional and cannot erase an actual current palpation observation.'); $hasCurrentPalpation = false; foreach ($index['sources'] as $source) { priorityScopeExpect($source['quote'] === $ledger['sources'][$source['id']]['quote'] && $source['scope'] === $ledger['sources'][$source['id']]['scope'] && $source['date'] === $ledger['sources'][$source['id']]['date'], 'Mandatory roles do not rewrite source text or time/scope.'); foreach ($source['spans'] as $span) if ($source['scope'] === 'current' && $span['role'] === 'palpation' && $span['recorded']) $hasCurrentPalpation = true; } priorityScopeExpect(serialize($ledger) === $before && $hasCurrentPalpation === ($caseNumber === 1) && $index['sources']['s7']['spans'][0]['role'] === 'pulse', 'Pulse-only and historical-only cases remain distinct from a real current palpation source; no observation is fabricated.'); } priorityScopeExpect(!class_exists('app\\adminapi\\service\\TcmClinicalFactStateGuard', false) && !class_exists('app\\adminapi\\service\\TcmClinicalEvidenceContract', false), 'Pure input tests do not load concurrent validation workers or claim model-output acceptance.'); echo "TCM priority/scope input prompt: {$checks} assertions passed (pure Index only; shared integration deferred).\n";