[ 'id' => $id, 'scope' => $scope, 'path' => $path, 'date' => '2026-04-02', 'field' => 'allergy_history', 'quote' => $quote, ]; $sources = [ 's1' => $source('s1', 'current', 'current.allergy_history', '本次询问无已知药物过敏史'), 's2' => $source('s2', 'historical', 'history.0.allergy_history', '当时询问无已知药物过敏史'), 's3' => $source('s3', 'current', 'chat.0.text', '自述无已知药物过敏史'), 's4' => $source('s4', 'current', 'current.family_history', '未记录'), 's5' => $source('s5', 'current', 'current.present_illness', '自述乏力'), ]; $assertions = []; foreach (['s1', 's2', 's3'] as $id) { $assertions[] = ['source_id' => $id, 'state' => 'negative', 'quote' => '无已知药物过敏史', 'scope' => $sources[$id]['scope'], 'date' => '2026-04-02']; } $assertions[] = ['source_id' => 's4', 'state' => 'unknown', 'quote' => '未记录']; $assertions[] = ['source_id' => 's5', 'state' => 'positive', 'quote' => '自述乏力']; $ledger = ['sources' => $sources, 'assertions' => $assertions]; $index = Index::build($ledger); $before = $index; $prompt = Index::prompt($index); $expect($index === $before, 'Input formatting cannot mutate the source index.'); $expect(preg_match('~\n(.*?)\n~s', $prompt, $match) === 1, 'Selection map exists.'); $map = json_decode($match[1], true, 512, JSON_THROW_ON_ERROR); $expect(count($map['roles']) === 6, 'Six original role entries remain.'); $expect($map['current_source_reference_texts'] === [[ 'source_id' => 's1', 'date' => '2026-04-02', 'reference_text' => '相关原文见s1', ]], 'Only an actual typed current denial receives a neutral reference; history, remote, unknown and positive are distinct.'); $expect(!isset($map['current_source_reference_texts'][0]['quote']), 'The aid does not rewrite a denial or create a shortened patient assertion.'); preg_match('~\n(.*?)\n~s', $prompt, $raw); $rows = json_decode($raw[1], true, 512, JSON_THROW_ON_ERROR); $expect(count($rows) === count($sources), 'All raw sources remain, including history and remote records.'); foreach ($rows as $row) { $expect($row['date'] === $sources[$row['id']]['date'], 'Original date remains.'); $expect($row['scope'] === $sources[$row['id']]['scope'], 'Original time scope remains.'); $expect($row['spans'][0]['quote'] === $sources[$row['id']]['quote'], 'Exact original quote and qualifiers remain.'); } $expect($index['sources']['s4']['negative_assertions'] === [], 'Unknown is not a recorded denial.'); $expect(str_contains($prompt, '不能删掉程度、已知或时间限定'), 'Qualifier retention is explicit during later reasoning.'); $expect(str_contains($prompt, '不能用来源核对代替完整分析'), 'A reference is not a substitute for a reasoned full report.'); $invalid = $index; $invalid['valid'] = false; preg_match('~\n(.*?)\n~s', Index::prompt($invalid), $bad); $badMap = json_decode($bad[1], true, 512, JSON_THROW_ON_ERROR); $expect($badMap['available'] === false && !isset($badMap['current_source_reference_texts']), 'Unavailable input cannot manufacture denial references.'); echo 'TCM recorded-denial input references: ' . $count . " assertions passed (pure input; no model-quality claim).\n";