[
'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";