push($chunk)) break; } $finished = $stream->finish(); factStateExpect($finished === $valid && $stream->rejected() === !$valid, $label . ': stream result matches validation'); if ($valid) factStateExpect($visible === $text . "\n\n", $label . ': accepted source bytes are preserved'); else factStateExpect(!str_contains($visible, $forbidden !== '' ? $forbidden : $text), $label . ': rejected observation never reaches the callback'); } $current = ['id' => 51, 'diagnosis_date' => '2026-10-23', 'chief_complaint' => '气短', 'present_illness' => '活动后气短,无胸痛;未记录固定刺痛或耳鸣。', 'tongue' => '舌淡红,苔少;无黄腻苔、紫暗或瘀斑。', 'pulse' => '脉细弱,按之无力;不浮、不滑。']; $ledger = TcmClinicalFactStateGuard::build($current); factStateExpect(count(array_filter($ledger['assertions'], static fn (array $row): bool => $row['state'] === 'unknown')) === 2, 'unknown list items are individually indexed without losing source text'); factStateExpect(in_array('positive', array_column($ledger['assertions'], 'state'), true) && in_array('negative', array_column($ledger['assertions'], 'state'), true), 'all three explicit source states coexist'); factStateCheck($ledger, '活动后气短。已记录“无胸痛”;固定刺痛或耳鸣未记录。', true, 'positive, explicit negative and unknown remain distinct'); factStateCheck($ledger, '无固定刺痛。', false, 'unknown is not an observed negative', 'UNKNOWN_TO_NEGATIVE'); factStateCheck($ledger, '无耳鸣。', false, 'enumerated unknown is not an observed negative', 'UNKNOWN_TO_NEGATIVE'); factStateCheck($ledger, '患者存在固定刺痛。', false, 'unknown is not a positive observation', 'UNKNOWN_TO_POSITIVE'); factStateCheck($ledger, '未见活动后气短。', false, 'recorded positive is not negative', 'POSITIVE_TO_NEGATIVE'); factStateCheck($ledger, '患者有胸痛。', false, 'recorded negative is not positive', 'NEGATIVE_TO_POSITIVE'); factStateCheck($ledger, '舌苔少而非腻。', false, 'a narrower negated compound cannot exclude the whole attribute', 'NEGATION_SCOPE_WIDENED'); factStateCheck($ledger, '无黄腻苔,舌无紫暗或瘀斑。', true, 'exact qualified source negation remains usable'); factStateCheck($ledger, '无黄腻。', true, 'omitting only the repeated object noun keeps its adjective scope'); factStateCheck($ledger, '皮肤未见异常皮疹。', false, 'an explicitly claimed physical negative needs a source even without an unknown label', 'NEGATIVE_NOT_IN_SOURCE'); factStateCheck($ledger, '肢体无颤抖或拘挛。', false, 'generic unsupported observed negatives are not invented to fill examination headings', 'NEGATIVE_NOT_IN_SOURCE'); factStateCheck($ledger, '若出现固定刺痛,应重新评估。', true, 'hypothetical future symptom is not asserted to exist'); factStateCheck($ledger, '请补问固定刺痛或耳鸣,不能把未记录视为无。', true, 'collection advice is not an observed negative'); factStateCheck($ledger, '当前无足够证据支持血瘀。', true, 'absence of evidence is not absence of a patient symptom'); factStateCheck($ledger, '因寒热属性不显,不宜把大苦寒清热、辛温助阳作为当前主方向。', true, 'qualitative interpretation does not turn a treatment list into observed negative findings'); factStateCheck($ledger, '脉细弱,按之无力;不浮、不滑。', true, 'lexical force adjective is not a separate clinical absence'); factStateCheck($ledger, '脉弦滑有力。', false, 'a new explicit pulse feature cannot be invented', 'PULSE_FEATURE_NOT_IN_CURRENT_SOURCE'); factStateCheck($ledger, '舌质红绛,苔黄腻。', false, 'new or polarity-reversed tongue features are rejected', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE'); factStateCheck($ledger, '脉弦、数尚未记录。', true, 'new pulse feature names can remain explicitly unknown'); factStateCheck($ledger, '结合语声低微、脉细弱按之无力,说明现有依据。', true, 'ordinary 结合 and voice 低微 do not invent pulse 结 or 微'); factStateCheck($ledger, '若后续脉弦滑、舌红绛,应重新辨证。', true, 'conditional future examinations are not current findings'); factStateCheck($ledger, '患者有轻微胸痛。', false, 'a qualified positive still conflicts with broad explicit source absence', 'NEGATIVE_TO_POSITIVE'); factStateStreamCheck($ledger, '本次无固定刺痛,若后续不适应复诊。', false, 'a conditional tail cannot exempt an actual earlier patient assertion', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($ledger, '无固定刺痛,可作为鉴别依据。', false, 'an evidence tail cannot exempt an actual earlier patient assertion', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($ledger, '无固定刺痛是鉴别依据。', false, 'an evidence word inside the same part cannot exempt a patient assertion', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($ledger, '无固定刺痛,无专门描述。', false, 'a later missing-description phrase cannot exempt the earlier patient negative', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($ledger, '固定刺痛须补问。', true, 'a postfix collection requirement is not a positive observation'); factStateStreamCheck($ledger, '本次不单凭病名选方。', true, 'a reasoning policy is not a physical negative'); factStateStreamCheck($ledger, '不移作闻诊所见。', true, 'an attribution policy is not a physical negative'); factStateStreamCheck($ledger, '若出现固定刺痛,应重新评估。', true, 'a true hypothetical is still not a patient observation'); factStateStreamCheck($ledger, '当前无足够证据支持血瘀。', true, 'pure absence of evidence remains a valid uncertainty statement'); $allergyUnknown = TcmClinicalFactStateGuard::build($current + ['allergy_history_text' => '未记录']); factStateStreamCheck($allergyUnknown, '过敏史:无药物过敏史。', false, 'Chinese allergy labels retain field-level unknown', 'UNKNOWN_TO_NEGATIVE', '无药物过敏史'); factStateStreamCheck($allergyUnknown, '过敏史:无。', false, 'a standalone absent value cannot erase a field unknown', 'UNKNOWN_TO_NEGATIVE', '无'); factStateStreamCheck($allergyUnknown, "过敏史:\n\n无。", false, 'a paragraph break cannot separate an unknown field from its false absent value', 'UNKNOWN_TO_NEGATIVE', '无'); factStateStreamCheck($ledger, '无药物过敏史。', false, 'a missing allergy field is unknown rather than a negative', 'UNKNOWN_TO_NEGATIVE', '无药物过敏史'); $examUnknown = TcmClinicalFactStateGuard::build(array_replace($current, ['tongue' => '未记录', 'pulse' => '不详'])); factStateStreamCheck($examUnknown, '舌正常。', false, 'a short tongue alias cannot turn unknown into normal', 'UNKNOWN_TO_POSITIVE', '正常'); factStateStreamCheck($examUnknown, '脉正常。', false, 'a short pulse alias cannot turn unknown into normal', 'UNKNOWN_TO_POSITIVE', '正常'); factStateStreamCheck($examUnknown, "舌:\n\n正常。", false, 'a tongue title and a value across paragraphs remain bound', 'UNKNOWN_TO_POSITIVE', '正常'); factStateStreamCheck($examUnknown, "### 脉象\n\n正常。", false, 'a Markdown field title also retains its value state', 'UNKNOWN_TO_POSITIVE', '正常'); factStateStreamCheck($allergyUnknown, '过敏史:未记录。', true, 'a truthful unknown field is accepted'); factStateStreamCheck($allergyUnknown, '无药物过敏史记录。', true, 'absence of an allergy record is not an observed absence of allergy'); factStateStreamCheck($allergyUnknown, '过敏史:没有记录。', true, 'a field may explicitly state that its record is unavailable'); factStateStreamCheck($allergyUnknown, '无论药物过敏史如何,均需核实。', true, 'regardless-of wording is not an observed negative'); factStateStreamCheck($examUnknown, "舌:\n\n未记录。", true, 'a truthful unknown remains accepted across paragraphs'); $allergyDenied = TcmClinicalFactStateGuard::build($current + ['allergy_history_text' => '本次询问无已知药物过敏史']); factStateStreamCheck($allergyDenied, '本次询问无已知药物过敏史。', true, 'an explicitly recorded denial is not recast as missing'); factStateStreamCheck($allergyDenied, "过敏史:\n\n无。", true, 'an explicitly denied history can retain its original field meaning'); $fieldDenied = TcmClinicalFactStateGuard::build($current + ['allergy_history_text' => '否认']); factStateStreamCheck($fieldDenied, '过敏史:无。', true, 'an explicit denial retains the context of its history field'); $fieldUnknownWithAdvice = TcmClinicalFactStateGuard::build($current + ['allergy_history_text' => '本次过敏史未记录,已告知复诊核实。']); factStateStreamCheck($fieldUnknownWithAdvice, '过敏史:无。', false, 'an administrative instruction does not turn an unknown history field into a recorded finding', 'UNKNOWN_TO_NEGATIVE', '无'); factStateStreamCheck($ledger, '家族史:无。', false, 'a missing family history is also unknown', 'UNKNOWN_TO_NEGATIVE', '无'); factStateStreamCheck($ledger, '手术史:无。', false, 'a missing surgery history is also unknown', 'UNKNOWN_TO_NEGATIVE', '无'); $familyDenied = TcmClinicalFactStateGuard::build($current + ['family_history_text' => '否认']); factStateStreamCheck($familyDenied, '家族史:无。', true, 'a clear text denial alias is preserved without interpreting a numeric code'); $emptyAliasDenied = TcmClinicalFactStateGuard::build($current + ['allergy_history' => '否认', 'allergy_history_text' => '']); factStateStreamCheck($emptyAliasDenied, '过敏史:无。', true, 'an empty display alias cannot hide an explicit raw field denial'); $surgeryUnknown = TcmClinicalFactStateGuard::build($current + ['surgery_history_text' => '未记录']); factStateStreamCheck($surgeryUnknown, "手术史\n\n无。", false, 'a text alias preserves unknown across a field heading', 'UNKNOWN_TO_NEGATIVE', '无'); $qualified = TcmClinicalFactStateGuard::build(['present_illness' => '无明显腹胀;未记录皮疹或手麻。']); factStateCheck($qualified, '无腹胀。', false, 'removing a degree qualifier widens the negative', 'NEGATION_SCOPE_WIDENED'); factStateCheck($qualified, '无明显腹胀。', true, 'preserved degree qualifier is accepted'); factStateCheck($qualified, '没有皮疹。', false, 'different symptoms and negation wording use the same generic rule', 'UNKNOWN_TO_NEGATIVE'); factStateCheck($qualified, '不伴手麻。', false, 'generic alternative negation wording is checked', 'UNKNOWN_TO_NEGATIVE'); $history = AiRequiredSource::submission(['diagnoses' => [ ['id' => 51, 'diagnosis_date' => '2026-10-23', 'present_illness' => '未记录头晕。'], ['id' => 9, 'diagnosis_date' => '2020-05-02', 'present_illness' => '无头晕。'], ]]); $temporal = TcmClinicalFactStateGuard::build(['id' => 51, 'diagnosis_date' => '2026-10-23', 'present_illness' => '未记录头晕。'], $history); factStateCheck($temporal, '本次无头晕。', false, 'historical negative cannot fill current unknown', 'UNKNOWN_TO_NEGATIVE'); factStateCheck($temporal, '既往2020-05-02无头晕,本次未记录头晕。', true, 'a dated historical quotation remains historical'); factStateCheck($temporal, '头晕本次未记录。', true, 'postfix unknown is preserved'); factStateCheck($temporal, '2020-05-02无头晕。', true, 'an explicit historical date is not silently applied to the current visit'); factStateCheck($temporal, "头晕未记录。\n\n无头晕。", false, 'correct uncertainty in one paragraph cannot excuse a later contradiction', 'UNKNOWN_TO_NEGATIVE'); $datedCurrent = ['id' => 61, 'diagnosis_date' => '2026-10-23', 'present_illness' => '无胸痛心悸;未记录耳鸣。']; $datedSubmission = ['required_content' => [['key' => 'daily_tracking', 'value' => ['exercise' => [ ['record_date' => '2026-10-22', 'note' => '活动后乏力、汗出较明显,无胸痛或晕厥;无耳鸣。'], ]]]]]; $dated = TcmClinicalFactStateGuard::build($datedCurrent, $datedSubmission); factStateCheck($dated, '2026-10-22活动记录见“活动后乏力、汗出较明显,无胸痛或晕厥”。', true, 'a full historical calendar date excludes the incompatible current compound on the next day'); factStateCheck($dated, '2026年10月22日活动记录见“活动后乏力、汗出较明显,无胸痛或晕厥”。', true, 'Chinese calendar notation has the same exact-day scope'); factStateCheck($dated, '2026-10-23无胸痛。', false, 'same-year historical support cannot widen a different current negative', 'NEGATION_SCOPE_WIDENED'); factStateCheck($dated, '2026-10-23本次“无耳鸣”。', false, 'a mismatched-date old quote cannot erase current uncertainty', 'UNKNOWN_TO_NEGATIVE'); factStateCheck($dated, '本次“无耳鸣”。', false, 'an undated historical quote cannot erase current uncertainty', 'UNKNOWN_TO_NEGATIVE'); factStateCheck($dated, '2026-10-22活动记录“无耳鸣”。', true, 'a correctly dated historical negative remains usable as history'); $sameDay = TcmClinicalFactStateGuard::build(['id' => 62, 'diagnosis_date' => '2026-10-23', 'present_illness' => '无明显腹胀。'], ['required_content' => [['key' => 'daily_tracking', 'value' => ['exercise' => [ ['record_date' => '2026-10-23', 'note' => '活动后无腹胀或头痛。'], ]]]]]); factStateCheck($sameDay, '2026-10-23活动记录“活动后无腹胀或头痛”。', true, 'a complete verified dated source quote is compared with its own scope rather than another context on that day'); factStateCheck($sameDay, '2026-10-23活动记录“无腹胀”。', false, 'a truncated source quote cannot authorize widened negation', 'NEGATION_SCOPE_WIDENED'); factStateCheck($sameDay, '2026-10-23活动记录“活动后无腹胀或头痛”;本次无腹胀。', false, 'source quote attribution never leaks to an unquoted current assertion', 'NEGATION_SCOPE_WIDENED'); $mixed = TcmClinicalFactStateGuard::build(['present_illness' => '偶有头晕,胸痛未记录,未提供手麻情况。']); factStateCheck($mixed, '无胸痛。', false, 'postfix unknown after a positive clause is indexed independently', 'UNKNOWN_TO_NEGATIVE'); factStateCheck($mixed, '有胸痛。', false, 'postfix unknown cannot become positive either', 'UNKNOWN_TO_POSITIVE'); factStateCheck($mixed, '患者有头晕,胸痛未记录。', true, 'adjacent mixed clauses preserve their own states'); $medications = TcmClinicalFactStateGuard::build(['current_medications' => '使用二甲双胍;未使用胰岛素或中药。']); factStateCheck($medications, '当前使用胰岛素。', false, 'explicit non-use is not silently rewritten as active therapy', 'NEGATIVE_TO_POSITIVE'); factStateCheck($medications, '使用二甲双胍,未使用胰岛素或中药。', true, 'recorded current treatment and non-use survive together'); $sourceOnly = TcmEvidenceGuard::assess($current); $menu = json_encode($sourceOnly['facts'], JSON_UNESCAPED_UNICODE); factStateExpect(str_contains($menu, '活动后气短,无胸痛;未记录固定刺痛或耳鸣。'), 'source menu no longer drops mixed positive/negative/unknown fields'); factStateExpect($sourceOnly['restricted'] === false && isset($sourceOnly['fact_state_ledger']), 'ledger presence does not change established examination completeness'); $unknownExams = TcmEvidenceGuard::assess(['chief_complaint' => '乏力', 'tongue' => '未记录', 'pulse' => '不详']); factStateExpect($unknownExams['restricted'] === true, 'retaining unknown quotations does not make an examination complete'); factStateCheck($unknownExams['fact_state_ledger'], '舌象无异常。', false, 'unknown examination is not normal', 'UNKNOWN_TO_NEGATIVE'); factStateCheck($unknownExams['fact_state_ledger'], '舌淡红,脉细。', false, 'missing examination text cannot produce observed tongue or pulse features', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE'); factStateCheck($unknownExams['fact_state_ledger'], '舌象未记录,脉象不详。', true, 'missing examination text remains unknown'); $fixture = CompleteTcmCase::current(); $fixtureLedger = TcmClinicalFactStateGuard::build($fixture, AiRequiredSource::submission(CompleteTcmCase::rows())); $artifactRoot = dirname(__DIR__, 2) . '/app/artifacts/ai_concurrency/'; $replayLocalArtifacts = getenv('TCM_TEST_LOCAL_REPLAYS') !== '0'; $samples = [ 'tcm_complete_v13_single_1_openai.md' => true, 'tcm_complete_v13_concurrent4_2_openai.md' => false, 'tcm_complete_v13_concurrent4_3_openai.md' => false, ]; if (!$replayLocalArtifacts) $samples = []; foreach ($samples as $file => $valid) { $path = $artifactRoot . $file; if (!is_file($path)) continue; $report = (string) file_get_contents($path); $result = TcmClinicalFactStateGuard::validate($fixtureLedger, $report); factStateExpect($result['valid'] === $valid, 'prior full report lexical state gate: ' . $file . ' ' . json_encode($result)); if (str_contains($file, 'concurrent4_2')) factStateExpect(factStateCode($result, 'UNKNOWN_TO_NEGATIVE'), 'prior unknown drift is explicitly rejected'); if (str_contains($file, 'concurrent4_3')) factStateExpect(factStateCode($result, 'NEGATION_SCOPE_WIDENED'), 'prior partial-negation drift is explicitly rejected'); foreach ($result['issues'] as $issue) factStateExpect(array_keys($issue) === ['code', 'source_id'] && preg_match('/^[A-Z_]+$/D', $issue['code']) === 1 && preg_match('/^(?:s[1-9][0-9]*|report)$/D', $issue['source_id']) === 1, 'issue metadata carries static codes and local IDs only'); } // Reproduce the private pilot's pure canonical source construction. These two // methods project/redact the supplied fixture only; they do not load records. $snapshotMethod = new ReflectionMethod(PatientAiReportLogic::class, 'buildSourceSnapshotFromRows'); $redactMethod = new ReflectionMethod(PatientAiReportLogic::class, 'sanitizeSnapshotForUpstream'); $pilotSubmission = $redactMethod->invoke(null, AiRequiredSource::submission($snapshotMethod->invoke(null, CompleteTcmCase::rows()))); $pilotLedger = TcmClinicalFactStateGuard::build($fixture, $pilotSubmission); // Required regressions are self-contained synthetic literals. Local artifacts // below are optional additional replays, never a clean-checkout dependency. factStateStreamCheck($pilotLedger, '本次舌质淡红,舌面少津,苔少;无黄腻苔、紫暗或瘀斑。脉细弱,按之无力;不浮、不滑、不紧。未记录腰酸耳鸣或固定刺痛。', true, 'independent literal preserves positive, negative and unknown source states'); factStateStreamCheck($pilotLedger, '血分辨析:未记录腰酸耳鸣或固定刺痛。现无固定刺痛。', false, 'independent prior drift literal still rejects unknown-to-negative', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($pilotLedger, '食欲一般、苔少而非腻。', false, 'independent prior drift literal still rejects widened negation', 'NEGATION_SCOPE_WIDENED', '非腻'); factStateStreamCheck($pilotLedger, '闻诊方面,语声低微,无喘促及异常气味。听其呼吸,平稳但气息不足,未见痰鸣音。', false, 'independent unsupported-observation literal is still rejected', 'NEGATIVE_NOT_IN_SOURCE', '未见痰鸣音'); factStateStreamCheck($pilotLedger, '**闻诊:**原文记“语声低微,无喘促及异常气味”。其他声音、气味未记录;“无咳嗽咯痰”为问诊原文,不移作闻诊所见。', true, 'independent attribution-policy literal no longer rejects faithful source quotations'); // v16: independent synthetic regressions; no local replay artifact is required. $instructionLedger = TcmClinicalFactStateGuard::build([ 'id' => 71, 'diagnosis_date' => '2026-10-23', 'chief_complaint' => '乏力', 'present_illness' => '无恶寒发热;未记录固定刺痛。', 'tongue' => '舌淡红,苔少;无黄腻苔。', 'pulse' => '脉细弱,不浮。', 'remark' => '无附件,不从图片或心率推断舌脉;未记录眼痛;无明显腹胀。', ]); $ruleSourceIds = array_keys(array_filter($instructionLedger['sources'], static fn (array $source): bool => $source['path'] === 'current.remark')); factStateExpect(!array_filter($instructionLedger['assertions'], static fn (array $assertion): bool => in_array($assertion['source_id'], $ruleSourceIds, true) && preg_match('/图片|心率|推断/u', $assertion['topic']) === 1), 'an inference rule in a source note never becomes a clinical polarity assertion'); factStateStreamCheck($instructionLedger, '本次“无恶寒发热”,脉“不浮”。', true, 'a pulse label starts its own assertion rather than inheriting the preceding absent marker'); factStateStreamCheck($instructionLedger, '本次“无恶寒发热”,脉细弱。', true, 'a positive pulse phrase also starts its own assertion'); factStateStreamCheck($instructionLedger, '当前无眼痛。', false, 'removing a source rule never removes adjacent unknown clinical notes', 'UNKNOWN_TO_NEGATIVE', '无眼痛'); factStateStreamCheck($instructionLedger, '绝无腹胀。', false, 'an actual widened degree negation in the same source note remains rejected', 'NEGATION_SCOPE_WIDENED', '绝无腹胀'); factStateStreamCheck($instructionLedger, '舌苔少而非腻。', false, 'an actual widened coating negation remains rejected', 'NEGATION_SCOPE_WIDENED', '非腻'); foreach (['后续若', '今后若', '此后若', '后续如果'] as $futurePrefix) { factStateStreamCheck($instructionLedger, '本次无恶寒发热,' . $futurePrefix . '新出现恶寒、发热或咽痛,应重新评估。', true, 'a localized future condition does not invent present findings: ' . $futurePrefix); factStateStreamCheck($instructionLedger, '本次无固定刺痛,' . $futurePrefix . '新出现咽痛,应重新评估。', false, 'a future tail cannot exempt a preceding current unknown-to-negative: ' . $futurePrefix, 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($instructionLedger, '本次出现恶寒发热,' . $futurePrefix . '出现咽痛,应重新评估。', false, 'a future tail cannot exempt a preceding actual current positive: ' . $futurePrefix, 'NEGATIVE_TO_POSITIVE', '出现恶寒发热'); } factStateStreamCheck($pilotLedger, '**表里:偏里证。**症状已持续3个月并逐渐出现,以“神疲乏力”“轻微活动后气短”“白天自汗”为主;本次又记录“无恶寒发热”,脉“不浮”,现有证据更支持内在功能失调,而非急性表证。但这只反映本次接诊状态,后续若新出现恶寒、发热、咽痛等,仍须重新判断。', true, 'independent full synthetic paragraph retains its source facts and future qualification'); // v17: source attribution regressions use only the committed synthetic case. factStateStreamCheck($pilotLedger, '访谈另记“没有最近高热、恶寒、腹泻”。', true, 'a complete interview quote binds to chat rather than a different current compound'); factStateStreamCheck($pilotLedger, '运动记录为“无胸痛或晕厥”。', true, 'a complete exercise quote binds to its exercise record'); factStateStreamCheck($pilotLedger, '运动记录另载“无胸痛或晕厥”。', true, 'an explicitly attributed exercise quote retains its original scope'); factStateStreamCheck($pilotLedger, '2026-10-22运动记录为“无胸痛或晕厥”。', true, 'an exact record date retains the exercise-source binding'); factStateStreamCheck($pilotLedger, '2026年10月22日运动记录另载“无胸痛或晕厥”。', true, 'the existing full-date syntax retains the same exercise-source binding'); factStateStreamCheck($pilotLedger, '腰酸耳鸣、固定刺痛为“未记录”,不能视为明确无,待补采。', true, 'the unknown-status explanation is not the source of the quotation conflict'); factStateStreamCheck($pilotLedger, '本次已记录阴性包括:“无恶寒发热”“无高热烦渴、汗出淋漓或意识异常”“无明显五心烦热、潮热盗汗”“无畏寒肢冷”“无咳嗽咯痰、胸痛心悸、浮肿或腹泻”。', true, 'unchanged compound current-source quotations retain their original negation scope'); factStateStreamCheck($pilotLedger, '运动记录为“无胸痛”。', false, 'a truncated exercise quote cannot widen the original compound', 'ATTRIBUTED_SOURCE_QUOTE_INVALID', '无胸痛'); factStateStreamCheck($pilotLedger, '运动记录为“无胸痛或呼吸困难”。', false, 'a modified exercise quote is rejected instead of gaining attribution', 'ATTRIBUTED_SOURCE_QUOTE_INVALID', '呼吸困难'); factStateStreamCheck($pilotLedger, '运动记录为“无胸痛或晕厥或手麻”。', false, 'an expanded exercise quote is rejected instead of gaining attribution', 'ATTRIBUTED_SOURCE_QUOTE_INVALID', '手麻'); factStateStreamCheck($pilotLedger, '运动记录为“没有最近高热、恶寒、腹泻”。', false, 'a chat quote cannot be relabeled as an exercise record', 'ATTRIBUTED_SOURCE_QUOTE_INVALID', '没有最近高热'); factStateStreamCheck($pilotLedger, '访谈另记“没有最近高热”。', false, 'a truncated interview scope is not a complete source-bound negative', 'ATTRIBUTED_SOURCE_QUOTE_INVALID', '没有最近高热'); factStateStreamCheck($pilotLedger, '当前运动记录为“无胸痛或晕厥”。', false, 'an explicit current claim cannot borrow a historical-source exception', 'NEGATION_SCOPE_WIDENED', '无胸痛'); factStateStreamCheck($pilotLedger, '运动记录为“无胸痛或晕厥”,当前无胸痛。', false, 'historical quote attribution cannot extend to the following current claim', 'NEGATION_SCOPE_WIDENED', '当前无胸痛'); factStateStreamCheck($pilotLedger, '访谈另记“没有最近高热、恶寒、腹泻”,当前无固定刺痛。', false, 'a faithful interview quote cannot exempt a current unknown-to-negative', 'UNKNOWN_TO_NEGATIVE', '当前无固定刺痛'); factStateStreamCheck($pilotLedger, '2026-10-21运动记录为“无胸痛或晕厥”。', false, 'an incorrect explicit record date cannot acquire a source binding', 'ATTRIBUTED_SOURCE_QUOTE_INVALID', '无胸痛'); factStateStreamCheck($qualified, '绝无腹胀。', false, 'degree qualifier broadening remains rejected', 'NEGATION_SCOPE_WIDENED', '绝无腹胀'); factStateStreamCheck($ledger, '舌苔少而非腻。', false, 'coating qualifier broadening remains rejected', 'NEGATION_SCOPE_WIDENED', '非腻'); // v18: a bounded conceptual comparison does not assert its right-hand tongue feature. factStateStreamCheck($pilotLedger, '舌淡红不等同于舌淡白。', true, 'only the explicit conceptual comparison target is excluded from observations'); factStateStreamCheck($pilotLedger, '舌淡红不等同于舌质淡白,不能据此确立血虚或阳虚。', true, 'a named tongue-quality comparison has the same local boundary'); factStateStreamCheck($pilotLedger, '**舌象:**本次面诊为“舌质淡红,舌面少津,苔少;无黄腻苔、紫暗或瘀斑”。少津、苔少支持津液不足倾向;舌淡红不等同于舌淡白,不能据此确立血虚或阳虚。', true, 'independent full synthetic comparison paragraph preserves grounded observations'); factStateStreamCheck($pilotLedger, '本例舌淡白。', false, 'an actual unrecorded pale-white tongue remains rejected', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '舌淡白'); factStateStreamCheck($pilotLedger, '本例舌淡白不等同于舌淡红。', false, 'an unsupported left-hand observation cannot borrow the target exception', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '舌淡白'); factStateStreamCheck($pilotLedger, '舌淡红不等同于舌淡白,但本例舌淡白。', false, 'the same feature asserted later is still checked', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '本例舌淡白'); factStateStreamCheck($pilotLedger, '舌淡红不等同于舌淡白;当前舌质红绛。', false, 'a later current feature across a clause boundary remains checked', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '当前舌质红绛'); factStateStreamCheck($pilotLedger, '本例舌淡白,不能据此确立血虚或阳虚。', false, 'an arbitrary cannot-conclude tail never exempts an actual observation', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '舌淡白'); factStateStreamCheck($pilotLedger, '舌淡红不等同于舌淡白红绛。', false, 'an unbounded target cannot consume additional feature tokens', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '红绛'); factStateStreamCheck($pilotLedger, '舌质淡红而非红绛。', false, 'a newly asserted negative tongue feature remains ungrounded', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '非红绛'); factStateStreamCheck($pilotLedger, '舌质淡红而非明确红绛。', false, 'an explicit degree word does not turn a new negative into a conceptual comparison', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '非明确红绛'); factStateStreamCheck($examUnknown, '舌淡红不等同于舌淡白。', false, 'a conceptual target cannot invent its unknown left-hand observation', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '舌淡红'); factStateStreamCheck($examUnknown, '舌淡红不等同于舌淡白;当前脉细。', false, 'unknown tongue and pulse still reject actual observations', 'PULSE_FEATURE_NOT_IN_CURRENT_SOURCE', '当前脉细'); factStateStreamCheck($pilotLedger, '本次无五心烦热、潮热盗汗。', false, 'the genuine removal of an observed-negative degree qualifier stays rejected', 'NEGATION_SCOPE_WIDENED', '无五心烦热'); factStateStreamCheck($pilotLedger, '望诊:活动后乏力、汗出较明显,无胸痛或晕厥。', false, 'unattributed historical exercise negatives cannot be moved into current inspection', 'NEGATION_SCOPE_WIDENED', '无胸痛'); // Bounded advice cannot inherit an earlier observed negative or become an exam. factStateStreamCheck($pilotLedger, '本次舌质淡红,舌面少津,苔少;无黄腻苔、紫暗或瘀斑。无舌像附件,不能补充裂纹、齿痕等未记录内容。', true, 'a strict data-invention prohibition is not an observed tongue feature'); factStateStreamCheck($pilotLedger, '此处“无喘促”指面诊当时闻诊表现,不能替代活动后呼吸困难程度或心肺功能评价。', true, 'an assessment limit never inherits the quoted absent marker'); factStateStreamCheck($pilotLedger, '不能补充裂纹等未记录内容,但本例舌裂纹。', false, 'a following actual finding is not exempt', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '舌裂纹'); factStateStreamCheck($pilotLedger, '本例舌裂纹,不能补充其他未记录内容。', false, 'a preceding actual finding is not exempt', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '舌裂纹'); factStateStreamCheck($examUnknown, '不能补充舌裂纹等未记录内容;当前脉滑。', false, 'unknown pulse stays unknown after an advisory', 'PULSE_FEATURE_NOT_IN_CURRENT_SOURCE', '当前脉滑'); factStateStreamCheck($pilotLedger, '当前无固定刺痛,不能替代后续检查。', false, 'an advisory cannot exempt a current unknown-to-negative', 'UNKNOWN_TO_NEGATIVE', '当前无固定刺痛'); factStateStreamCheck($pilotLedger, '不能补充裂纹等未记录内容且本例舌裂纹。', false, 'only a full bounded advisory can be exempt', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '舌裂纹'); factStateStreamCheck($pilotLedger, '望诊:未提供皮肤润燥资料,无舌照片或其他附件。', true, 'missing attachment metadata is not a patient negative finding'); factStateStreamCheck($pilotLedger, '望诊:无舌照片,当前舌质淡白。', false, 'attachment absence cannot exempt a following actual tongue claim', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '舌质淡白'); factStateStreamCheck($pilotLedger, '闻诊:无其他附件,未见痰鸣音。', false, 'attachment absence cannot exempt an unsupported clinical negative', 'NEGATIVE_NOT_IN_SOURCE', '未见痰鸣音'); factStateStreamCheck($pilotLedger, '当前无固定刺痛,无其他附件。', false, 'attachment metadata cannot change the unknown clinical state', 'UNKNOWN_TO_NEGATIVE', '当前无固定刺痛'); foreach (['聊天记录另载', '聊天内容记载', '访谈记载'] as $label) { factStateStreamCheck($pilotLedger, $label . '“没有最近高热、恶寒、腹泻”。', true, 'standard chat attribution aliases bind only complete original quotations'); } factStateStreamCheck($pilotLedger, '运动时“无胸痛或晕厥”。', true, 'an explicitly attributed exercise-event quotation binds only its complete original source'); factStateStreamCheck($pilotLedger, '聊天记录另载“没有最近高热”。', false, 'chat aliases cannot exempt a truncated source quote', 'ATTRIBUTED_SOURCE_QUOTE_INVALID', '没有最近高热'); factStateStreamCheck($pilotLedger, '运动时“无胸痛或晕厥”,当前无固定刺痛。', false, 'an event quote cannot exempt a following current unknown-to-negative', 'UNKNOWN_TO_NEGATIVE', '当前无固定刺痛'); factStateStreamCheck($pilotLedger, '当前运动时“无胸痛或晕厥”。', false, 'a historical event quote cannot establish a current absence', 'NEGATION_SCOPE_WIDENED', '无胸痛'); // v23: provenance/capability text does not become a clinical observation. $hearingSource = array_values(array_filter($pilotLedger['sources'], static fn (array $row): bool => $row['path'] === 'current.symptoms'))[0]; foreach (['现病史', 'symptoms', 'current.symptoms', $hearingSource['id']] as $sourceLabel) { factStateStreamCheck($pilotLedger, '闻诊:语声低微,无喘促及异常气味(来源:' . $sourceLabel . ')。', true, 'a complete annotation names an existing group, field, path or source ID: ' . $sourceLabel); } factStateStreamCheck($pilotLedger, '闻诊:语声低微,无喘促及异常气味(来源:现病史)。', true, 'ASCII parentheses use the same bounded metadata grammar'); factStateStreamCheck($pilotLedger, '闻诊:无皮疹(来源:现病史)。', false, 'valid provenance never supplies an unrecorded clinical absence', 'NEGATIVE_NOT_IN_SOURCE', '无皮疹'); factStateStreamCheck($pilotLedger, '闻诊:语声低微,无喘促及异常气味(来源:不存在分组)。', false, 'an unknown source label is not silently stripped', 'NEGATIVE_NOT_IN_SOURCE', '无喘促'); factStateStreamCheck($pilotLedger, '闻诊:语声低微,无喘促及异常气味(来源:现病史)(患者无固定刺痛)。', false, 'clinical content in a second parenthesis is still checked', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($pilotLedger, '闻诊:语声低微,无喘促及异常气味(来源:现病史,患者无固定刺痛)。', false, 'a source prefix cannot turn a mixed clinical parenthesis into metadata', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($pilotLedger, '一、四诊 望诊:神疲乏力,面色少华(来源:现病史)。 闻诊:语声低微,无喘促及异常气味(来源:现病史)。 问诊:主诉乏力、动则气短、自汗、口干3个月。现病史见轻微活动后气短,白天自汗、活动后加重,口干欲饮、少量频饮。食欲一般,大便稍干、每日1次,小便量较以往略多、夜尿1次。睡眠一般。 明确阴性:无恶寒发热,无高热烦渴、汗出淋漓或意识异常。无明显五心烦热、潮热盗汗,无畏寒肢冷。无咳嗽咯痰、胸痛心悸、浮肿或腹泻。未记录腰酸耳鸣或固定刺痛(来源:现病史)。 舌象:舌质淡红,舌面少津,苔少;无黄腻苔、紫暗或瘀斑(来源:诊断信息)。 脉象:脉细弱,按之无力;不浮、不滑、不紧(来源:诊断信息)。', true, 'portable full synthetic four-diagnosis paragraph preserves all stated source scopes'); factStateStreamCheck($pilotLedger, '**舌象:**本次文字记录为“舌质淡红,舌面少津,苔少”;已记录阴性为“无黄腻苔、紫暗或瘀斑”。因无舌照,不能复核舌体胖瘦、齿痕、裂纹及舌下络脉。', true, 'portable full synthetic tongue paragraph distinguishes observations from a review limit'); factStateStreamCheck($examUnknown, '因无舌照,不能复核舌体胖瘦、齿痕、裂纹及舌下络脉。', true, 'a strict review limit neither fabricates nor fills an unknown examination'); factStateStreamCheck($examUnknown, '因无舌照,不能复核齿痕,当前舌质淡红。', false, 'a review limit cannot fill an unknown tongue with a later observation', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '舌质淡红'); factStateStreamCheck($pilotLedger, '因无舌照,不能复核齿痕,当前舌无裂纹。', false, 'actual absence after a bounded review limit remains unsupported', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '舌无裂纹'); factStateStreamCheck($pilotLedger, '因无舌照,不能复核齿痕,当前舌有齿痕。', false, 'actual presence after a bounded review limit remains unsupported', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '舌有齿痕'); factStateStreamCheck($pilotLedger, '本例舌无裂纹,因无舌照,不能复核齿痕。', false, 'a preceding actual absence cannot borrow a later review limit', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '舌无裂纹'); factStateStreamCheck($pilotLedger, '不能复核齿痕且本例舌裂纹。', false, 'the review-limit grammar cannot consume an asserted feature tail', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '舌裂纹'); $recordedReview = TcmClinicalFactStateGuard::build(['tongue' => '舌质淡红,有齿痕,无裂纹。']); factStateStreamCheck($recordedReview, '舌质淡红,有齿痕;无裂纹。因无舌照,不能复核齿痕、裂纹。', true, 'inability to recheck does not relabel known positive or negative features as unknown'); factStateStreamCheck($pilotLedger, '四诊所示阴虚热象不突出,舌质淡红。', true, 'a new tongue subject does not inherit a reasoning predicate'); factStateStreamCheck($pilotLedger, '四诊所示阴虚热象不突出,舌质并非淡红。', false, 'an actual new tongue negation still contradicts the recorded positive', 'POSITIVE_TO_NEGATIVE', '并非淡红'); factStateStreamCheck($pilotLedger, '四诊所示阴虚热象不突出,舌质淡红而非明显红绛。', false, 'fixing a preceding reasoning scope does not approve a new red-crimson negative', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '非明显红绛'); factStateStreamCheck($pilotLedger, '乏力、气短和出汗也可能存在贫血、甲状腺、心肺或自主神经等非中医证候原因,中药不能替代必要排查。', true, 'a different-subject safety sentence is not current herbal use'); factStateStreamCheck($pilotLedger, '当前使用中药,中药不能替代必要排查。', false, 'actual herbal use preceding a safety statement remains a contradiction', 'NEGATIVE_TO_POSITIVE', '使用中药'); factStateStreamCheck($pilotLedger, '可能存在贫血,中药不能替代必要排查,当前使用中药。', false, 'actual herbal use following a safety statement remains a contradiction', 'NEGATIVE_TO_POSITIVE', '使用中药'); factStateStreamCheck($pilotLedger, '患者未记录心悸等其他常见相关表现,但这不否定现有主方向。', false, 'an explicitly denied named symptom cannot be recast as unknown', 'NEGATIVE_TO_UNKNOWN', '未记录心悸'); factStateStreamCheck($pilotLedger, '其他声音、心悸未记录。', false, 'a general other-findings prefix cannot exempt a named known symptom', 'NEGATIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($pilotLedger, '胸痛心悸未记录。', false, 'an entire known negative compound cannot be erased', 'NEGATIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($pilotLedger, '口干未记录。', false, 'a known positive symptom cannot be recast as unknown', 'POSITIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($pilotLedger, '舌象未记录。', false, 'an exactly named known examination field cannot become unknown', 'RECORDED_TO_UNKNOWN', '未记录'); factStateStreamCheck($pilotLedger, '固定刺痛未记录;脉亦未记录数象。', true, 'genuine missing symptoms and pulse features retain unknown state'); factStateStreamCheck($pilotLedger, '本次无固定刺痛。', false, 'reverse-state checks do not weaken unknown-to-negative rejection', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($pilotLedger, '舌质并非鲜红,现有舌象不足以确定明显阴虚内热。', false, 'the reproduced fresh-red negative is not silently outside the vocabulary', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '并非鲜红'); $freshRedRecorded = TcmClinicalFactStateGuard::build(['tongue' => '舌质鲜红。']); factStateStreamCheck($freshRedRecorded, '舌质鲜红。', true, 'the new surface term remains usable when explicitly recorded'); factStateStreamCheck($freshRedRecorded, '鲜红未记录。', false, 'a known fresh-red positive also retains its recorded state', 'POSITIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($freshRedRecorded, '舌质并非鲜红。', false, 'a new surface term also retains source polarity', 'POSITIVE_TO_NEGATIVE', '并非鲜红'); $freshRedDenied = TcmClinicalFactStateGuard::build(['tongue' => '舌质淡红;无鲜红。']); factStateStreamCheck($freshRedDenied, '舌质淡红;无鲜红。', true, 'an explicit fresh-red negative is allowed when the source actually records it'); factStateStreamCheck($pilotLedger, '**反证与局限:**四诊所示阴虚热象不突出,舌质淡红而非明显红绛,脉亦未记录数象,因此不应将本方解释为治疗阴虚火旺。患者未记录心悸等其他常见相关表现,但这不否定现有主方向。乏力、气短和出汗也可能存在贫血、甲状腺、心肺或自主神经等非中医证候原因,中药不能替代必要排查。', false, 'the portable full v21-like paragraph remains invalid after removing its parser false positives', 'NEGATIVE_TO_UNKNOWN', '未记录心悸'); // v24: attachment nouns, image capability limits and a genuine bare 待补. factStateStreamCheck($pilotLedger, '**望诊:**本次面诊见“神疲、面色少华”。生命体征:体温36.5℃,血压124/76mmHg,心率78次/分;身高170cm、体重65kg、BMI 22.5kg/m²。无舌照或其他图像附件,不能据图片补充面色、形体及局部体征。', true, 'portable full synthetic observation paragraph keeps the attachment capability limit local'); factStateStreamCheck($pilotLedger, '**舌象:**本次医师记录“舌质淡红,舌面少津,苔少;无黄腻苔、紫暗或瘀斑”。无图像可供复核,舌体胖瘦、齿痕、裂纹及舌下络脉待补。', true, 'portable full synthetic tongue paragraph uses bare postfix missing-data state'); factStateStreamCheck($examUnknown, '无舌照或其他图像附件,不能据图片补充面色、形体及局部体征。', true, 'file absence and a capability limit do not fabricate unknown examinations'); factStateStreamCheck($pilotLedger, '无图像可供复核,齿痕、裂纹待补。', true, 'a preceding file absence does not turn missing features into observed negatives'); factStateStreamCheck($pilotLedger, '无舌照或其他图像附件,不能据图片补充面色、形体及局部体征,当前舌无裂纹。', false, 'a following actual negative is outside the bounded capability limit', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '舌无裂纹'); factStateStreamCheck($pilotLedger, '本例舌有裂纹,无舌照或其他图像附件,不能据图片补充面色。', false, 'a preceding actual positive is outside the file metadata exemption', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '舌有裂纹'); factStateStreamCheck($pilotLedger, '不能据图片补充面色且本例舌裂纹。', false, 'an appended actual clause cannot masquerade as a capability list', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '舌裂纹'); factStateStreamCheck($pilotLedger, '望诊:无其他图像附件,患者无固定刺痛。', false, 'attachment noun suffixes do not exempt a subsequent unknown-to-negative', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($pilotLedger, '望诊:无其他图像附件及皮疹。', false, 'a metadata list cannot consume a clinical negative item', 'NEGATIVE_NOT_IN_SOURCE', '皮疹'); factStateStreamCheck($pilotLedger, '无图像可供复核,齿痕、裂纹待补,舌有裂纹。', false, 'postfix missing-data scope ends before a later actual feature', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '舌有裂纹'); factStateStreamCheck($pilotLedger, '无图像可供复核,齿痕待补,患者有心悸。', false, 'postfix missing-data scope cannot conceal a known-negative symptom becoming positive', 'NEGATIVE_TO_POSITIVE', '有心悸'); factStateStreamCheck($pilotLedger, '齿痕待补,患者无固定刺痛。', false, 'postfix missing-data scope cannot conceal an unknown symptom becoming negative', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($pilotLedger, '心悸待补。', false, 'bare postfix missing state cannot overwrite an explicitly denied symptom', 'NEGATIVE_TO_UNKNOWN', '待补'); factStateStreamCheck($pilotLedger, '口干待补。', false, 'bare postfix missing state cannot overwrite a positive symptom', 'POSITIVE_TO_UNKNOWN', '待补'); factStateStreamCheck($pilotLedger, '舌象待补。', false, 'a recorded tongue field cannot become bare missing data', 'RECORDED_TO_UNKNOWN', '待补'); factStateStreamCheck($pilotLedger, '脉待补。', false, 'a recorded short pulse alias cannot become bare missing data', 'RECORDED_TO_UNKNOWN', '待补'); factStateStreamCheck($pilotLedger, '舌象:待补。', false, 'a field label does not turn missing-data wording into blanket collection advice', 'RECORDED_TO_UNKNOWN', '待补'); factStateStreamCheck($pilotLedger, '待补脉象。', false, 'a prefix bare missing marker also retains the known field state', 'RECORDED_TO_UNKNOWN', '待补'); factStateStreamCheck($pilotLedger, "舌象:" . chr(10) . chr(10) . "待补。", false, 'a known field title remains bound across a paragraph break', 'RECORDED_TO_UNKNOWN', '待补'); factStateStreamCheck($examUnknown, "舌象:" . chr(10) . chr(10) . "待补。", true, 'a genuinely unknown field can retain bare missing state across paragraphs'); factStateStreamCheck($pilotLedger, '固定刺痛待补;待补齿痕。', true, 'genuine missing source items retain unknown state in either word order'); factStateStreamCheck($recordedReview, '齿痕、裂纹待补。', false, 'bare missing state does not erase explicitly recorded positive or negative features', 'POSITIVE_TO_UNKNOWN', '待补'); factStateExpect(factStateCode(TcmClinicalFactStateGuard::validate($recordedReview, '齿痕、裂纹待补。'), 'NEGATIVE_TO_UNKNOWN'), 'a known negative feature in the same list also retains its recorded state'); factStateStreamCheck($pilotLedger, '舌质淡红非青紫。', false, 'a new color negative remains rejected after attachment fixes', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '非青紫'); factStateStreamCheck($pilotLedger, '本例无痰多。', false, 'a new patient sputum negative remains rejected after attachment fixes', 'NEGATIVE_NOT_IN_SOURCE', '无痰多'); factStateStreamCheck($pilotLedger, '舌质淡红而非明确红绛。', false, 'a new explicit red-crimson negative remains rejected', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '非明确红绛'); // v25: strict bare citations, copular unknowns and a closed hearing reference. foreach (['(s3)', '(s2, s3)', '(s2, s3)', '(2026-10-23,[s3])', '(s3,2026年10月23日)', '(2026-10-23)'] as $citation) { factStateStreamCheck($pilotLedger, '闻诊:“无喘促及异常气味”' . $citation . '。', true, 'a complete source annotation contains only existing IDs and a verified date: ' . $citation); } foreach (['(s999)', '(s3,s999)', '(2026-10-22,[s3])', '(2026-13-99,s3)', '(2026-10-23,2026-10-22,s3)', '(2026-10-22)'] as $citation) { factStateStreamCheck($pilotLedger, '闻诊:“无喘促及异常气味”' . $citation . '。', false, 'unknown IDs or inconsistent dates cannot be removed as trusted metadata: ' . $citation, 'SOURCE_ANNOTATION_INVALID', '无喘促'); } factStateStreamCheck($pilotLedger, '闻诊:无喘促及异常气味(s3,患者无固定刺痛)。', false, 'a mixed citation cannot strip a patient unknown-to-negative', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($pilotLedger, '闻诊:无喘促及异常气味(s3,患者有心悸)。', false, 'a mixed citation cannot strip an actual positive assertion', 'NEGATIVE_TO_POSITIVE', '有心悸'); factStateStreamCheck($pilotLedger, '闻诊:无喘促及异常气味([s3,患者有心悸])。', false, 'nested citation brackets do not erase clinical content inside a mixed parenthesis', 'NEGATIVE_TO_POSITIVE', '有心悸'); factStateStreamCheck($pilotLedger, '闻诊:无喘促及异常气味([s3,患者无固定刺痛])。', false, 'nested brackets do not erase an unknown-to-negative inside a mixed parenthesis', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($pilotLedger, '闻诊:语声低微,无喘促及异常气味(s2, s3)。本次舌质淡红,舌面少津,苔少(s9);无黄腻苔、紫暗或瘀斑(s9)。未记录腰酸耳鸣或固定刺痛(s2)。', true, 'portable synthetic negative list retains its source states beside bare existing IDs'); $historicalSource = array_values(array_filter($dated['sources'], static fn (array $row): bool => str_starts_with($row['path'], 'daily_tracking.exercise.')))[0]; factStateStreamCheck($dated, '运动记录为“无胸痛或晕厥”(2026-10-22,' . $historicalSource['id'] . ')。', true, 'a correct historical citation retains its event date and original compound scope'); factStateStreamCheck($dated, '运动记录为“无胸痛或晕厥”(2026-10-23,' . $historicalSource['id'] . ')。', false, 'a historical source cannot be assigned the current date', 'SOURCE_ANNOTATION_INVALID', '无胸痛'); factStateStreamCheck($dated, '本次无耳鸣(2026-10-22,' . $historicalSource['id'] . ')。', false, 'a valid historical citation cannot wash an explicitly current patient absence', 'NEGATIVE_NOT_IN_SOURCE', '无耳鸣'); factStateStreamCheck($dated, '运动记录为“无胸痛”(2026-10-22,' . $historicalSource['id'] . ')。', false, 'valid metadata does not approve a truncated historical negative quote', 'ATTRIBUTED_SOURCE_QUOTE_INVALID', '无胸痛'); foreach (['属', '为', '属于'] as $copula) { factStateStreamCheck($pilotLedger, '腰酸耳鸣、固定刺痛' . $copula . '“未记录腰酸耳鸣或固定刺痛”(s2),属于未知,不能作为肯定或否定依据。', true, 'a copular explanation binds exactly the same named unknown subjects: ' . $copula); } factStateStreamCheck($pilotLedger, '腰酸耳鸣、固定刺痛属“未记录腰酸耳鸣或固定刺痛”,是未知而非否认。', true, 'the alternate closed unknown explanation keeps its full tail'); factStateStreamCheck($pilotLedger, '腰酸耳鸣、固定刺痛为未记录腰酸耳鸣或固定刺痛,属于未知。', true, 'unknown-state binding does not depend on decorative quotation marks'); factStateStreamCheck($pilotLedger, '患者有腰酸耳鸣、固定刺痛为“未记录腰酸耳鸣或固定刺痛”。', false, 'a copula cannot erase an original explicit positive predicate', 'UNKNOWN_TO_POSITIVE', '有腰酸耳鸣'); factStateStreamCheck($pilotLedger, '患者出现固定刺痛属“未记录固定刺痛”。', false, 'an occurrence predicate remains an actual assertion before the copula', 'UNKNOWN_TO_POSITIVE', '出现固定刺痛'); factStateStreamCheck($pilotLedger, '固定刺痛属“未记录固定刺痛”,患者有固定刺痛。', false, 'a following explicit positive cannot borrow the unknown explanation', 'UNKNOWN_TO_POSITIVE', '有固定刺痛'); factStateStreamCheck($pilotLedger, '固定刺痛属“未记录固定刺痛”,本例舌质淡白。', false, 'an implicit actual examination after the comma stays outside unknown scope', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '舌质淡白'); factStateStreamCheck($pilotLedger, '腰酸耳鸣为“未记录固定刺痛”。', false, 'different subject and unknown payload cannot acquire the copular binding', 'UNKNOWN_TO_POSITIVE', '腰酸耳鸣'); factStateStreamCheck($pilotLedger, '心悸为“未记录心悸”,属于未知。', false, 'same-subject binding still checks a known negative recast as unknown', 'NEGATIVE_TO_UNKNOWN', '未记录心悸'); factStateStreamCheck($pilotLedger, '口干为“未记录口干”。', false, 'same-subject binding also preserves known positives', 'POSITIVE_TO_UNKNOWN', '未记录口干'); $hearingReference = '此处仅表示闻诊当时未见上述表现'; factStateStreamCheck($pilotLedger, '**闻诊:**本次记载语声低微,可作为气虚倾向的支持。“无喘促及异常气味”(2026-10-23,[s3]);' . $hearingReference . ',不能替代活动状态下呼吸评估。', true, 'portable full hearing paragraph binds a closed reference to the adjacent verified negative'); factStateStreamCheck($pilotLedger, '“无喘促及异常气味”(s3);' . $hearingReference . '。', true, 'an adjacent exact current-source negative supports the closed reference'); factStateStreamCheck($pilotLedger, $hearingReference . '。', false, 'the closed reference cannot appear without an antecedent', 'UNSUPPORTED_NEGATIVE_REFERENCE', '未见上述表现'); factStateStreamCheck($pilotLedger, '“语声低微”(s3);' . $hearingReference . '。', false, 'a positive quoted antecedent cannot become a negative by reference', 'UNSUPPORTED_NEGATIVE_REFERENCE', '未见上述表现'); factStateStreamCheck($pilotLedger, '“未记录固定刺痛”(s2);' . $hearingReference . '。', false, 'an unknown quoted antecedent cannot become a negative by reference', 'UNSUPPORTED_NEGATIVE_REFERENCE', '未见上述表现'); factStateStreamCheck($pilotLedger, '“无喘促及异常气味”(s2);' . $hearingReference . '。', false, 'a known but wrong source ID cannot verify the hearing antecedent', 'UNSUPPORTED_NEGATIVE_REFERENCE', '未见上述表现'); factStateStreamCheck($pilotLedger, '“无喘促及异常气味”(2026-10-22,s3);' . $hearingReference . '。', false, 'a mismatched date never gets washed away before reference validation', 'SOURCE_ANNOTATION_INVALID', '无喘促'); factStateStreamCheck($pilotLedger, '“无喘促及异常气味”(s3);' . $hearingReference . '(2026-10-22,s3)。', false, 'the referring clause cannot switch the supported source date', 'SOURCE_ANNOTATION_INVALID', '未见上述表现'); factStateStreamCheck($pilotLedger, '“无喘促及异常气味”(s3);' . $hearingReference . '(s2)。', false, 'the referring clause cannot switch to a different existing source', 'UNSUPPORTED_NEGATIVE_REFERENCE', '未见上述表现'); factStateStreamCheck($pilotLedger, '“无喘促及异常气味”(s3);食欲一般;' . $hearingReference . '。', false, 'an intervening statement clears the closed antecedent', 'UNSUPPORTED_NEGATIVE_REFERENCE', '未见上述表现'); factStateStreamCheck($pilotLedger, '“无喘促及异常气味”(s3);' . $hearingReference . ',患者有心悸。', false, 'the reference cannot exempt a later actual positive assertion', 'NEGATIVE_TO_POSITIVE', '有心悸'); factStateStreamCheck($pilotLedger, '“无喘促及异常气味”(s3);' . $hearingReference . ',患者无固定刺痛。', false, 'the reference cannot exempt a later actual unknown-to-negative', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); // v26: the three parser fixes remain portable without retained live artifacts. $dyspneaReference = '该记录支持气短尚未表现为闻诊可见的喘促'; $dyspneaParagraph = '**闻诊:**语声低微。四诊已记录阴性:“无喘促及异常气味”(s3,2026-10-23)。' . $dyspneaReference . ',但不能替代活动时呼吸频率、语句连续性及肺部听诊。'; factStateStreamCheck($pilotLedger, $dyspneaParagraph, true, 'portable full hearing paragraph keeps a bounded non-occurrence interpretation tied to its source'); factStateStreamCheck($pilotLedger, '“无喘促及异常气味”(2026-10-23,s3);' . $dyspneaReference . '。', true, 'a verified hearing negative supports only the closed dyspnea interpretation'); foreach (['', '“语声低微”(s3);', '“未记录固定刺痛”(s2);', '“无喘促及异常气味”(s2);', '“无喘促及异常气味”(s3);食欲一般;'] as $antecedent) { factStateStreamCheck($pilotLedger, $antecedent . $dyspneaReference . '。', false, 'a closed dyspnea interpretation requires an adjacent exact hearing negative: ' . $antecedent, 'UNSUPPORTED_NEGATIVE_REFERENCE', '尚未表现为'); } factStateStreamCheck($pilotLedger, '“无喘促及异常气味”(2026-10-22,s3);' . $dyspneaReference . '。', false, 'an incorrect antecedent date is not discarded before dyspnea reference binding', 'SOURCE_ANNOTATION_INVALID', '无喘促'); factStateStreamCheck($pilotLedger, '“无喘促及异常气味”(s3);' . $dyspneaReference . '(s2)。', false, 'the dyspnea interpretation cannot change its hearing source', 'UNSUPPORTED_NEGATIVE_REFERENCE', '尚未表现为'); factStateStreamCheck($pilotLedger, '“无喘促及异常气味”(s3);' . $dyspneaReference . '(2026-10-22,s3)。', false, 'the dyspnea interpretation cannot change its source date', 'SOURCE_ANNOTATION_INVALID', '尚未表现为'); $qualifiedHearing = TcmClinicalFactStateGuard::build(['chief_complaint' => '气短', 'symptoms' => '闻诊无明显喘促及异常气味。']); factStateStreamCheck($qualifiedHearing, '“无明显喘促及异常气味”;' . $dyspneaReference . '。', false, 'the reference cannot delete the hearing source degree qualifier', 'UNSUPPORTED_NEGATIVE_REFERENCE', '尚未表现为'); $otherHearing = TcmClinicalFactStateGuard::build(['chief_complaint' => '气短', 'symptoms' => '闻诊无异常气味。']); factStateStreamCheck($otherHearing, '“无异常气味”;' . $dyspneaReference . '。', false, 'a different hearing absence cannot supply an absent dyspnea target', 'UNSUPPORTED_NEGATIVE_REFERENCE', '尚未表现为'); $hearingWithoutDyspnea = TcmClinicalFactStateGuard::build(['symptoms' => '闻诊无喘促及异常气味。']); factStateStreamCheck($hearingWithoutDyspnea, '“无喘促及异常气味”;' . $dyspneaReference . '。', false, 'a closed interpretation cannot invent its positive gas-shortness subject', 'UNSUPPORTED_NEGATIVE_REFERENCE', '尚未表现为'); factStateStreamCheck($pilotLedger, '“无喘促及异常气味”(s3);' . $dyspneaReference . ',患者有心悸。', false, 'a following patient positive cannot borrow the dyspnea reference', 'NEGATIVE_TO_POSITIVE', '有心悸'); factStateStreamCheck($pilotLedger, '“无喘促及异常气味”(s3);' . $dyspneaReference . ',患者无固定刺痛。', false, 'a following patient negative cannot borrow the dyspnea reference', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($pilotLedger, '“无喘促及异常气味”(s3);' . $dyspneaReference . ',但不能替代后续检查,患者有心悸。', false, 'an advisory tail after the closed reference does not hide a later actual patient positive', 'NEGATIVE_TO_POSITIVE', '有心悸'); factStateStreamCheck($pilotLedger, '“无喘促及异常气味”(s3);' . $dyspneaReference . '且患者有喘促。', false, 'an asserted tail does not fit the closed reference grammar', 'NEGATIVE_TO_POSITIVE', '有喘促'); factStateStreamCheck($pilotLedger, '**切诊:**汗出及四肢温度的触诊、腹诊、按诊未记录,待补。', true, 'a portable complete palpation paragraph names missing examination records rather than absent sweat symptoms'); factStateStreamCheck($pilotLedger, '汗出的触诊未记录。', true, 'an explicit method noun does not relabel the recorded symptom itself'); factStateStreamCheck($pilotLedger, '口干及心悸的触诊、腹诊未记录。', true, 'a coordinated method subject retains its own record scope'); factStateStreamCheck($pilotLedger, '汗出未记录。', false, 'without a method noun an explicit source symptom still cannot be recast as unknown', 'NEGATIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($pilotLedger, '汗出及四肢温度未记录。', false, 'a coordinated symptom list without a method noun retains source state checks', 'NEGATIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($pilotLedger, '汗出及四肢温度的触诊、心悸未记录。', false, 'a method noun cannot absorb a later known negative symptom', 'NEGATIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($pilotLedger, '口干、汗出及四肢温度的触诊未记录。', false, 'a method noun cannot absorb an earlier known positive symptom', 'POSITIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($pilotLedger, '心悸的触诊及口干未记录。', false, 'a symptom after the method noun is not hidden in its coordinated subject', 'POSITIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($pilotLedger, '汗出及四肢温度的触诊未记录,患者无固定刺痛。', false, 'a missing method record never authorizes an actual negative tail', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($pilotLedger, '患者有心悸,汗出及四肢温度的触诊未记录。', false, 'an actual positive before the method record is checked independently', 'NEGATIVE_TO_POSITIVE', '有心悸'); $knownMethod = TcmClinicalFactStateGuard::build(['present_illness' => '记录汗出的触诊。']); factStateStreamCheck($knownMethod, '汗出的触诊未记录。', false, 'a method record explicitly present in the source also cannot be recast as unknown', 'POSITIVE_TO_UNKNOWN', '未记录'); $sameDayUnknownParagraph = '**问诊:**主症持续3个月,为神疲乏力、轻微活动后气短、白天自汗且活动后加重,并有口干欲饮、少量频饮。' . '食欲一般,大便稍干、每日1次,小便量较以往略多、夜尿1次,睡眠一般。四诊已记录阴性:“无恶寒发热,”(2026-10-23,s2);' . '“无高热烦渴、汗出淋漓或意识异常”(同日,s2);“无明显五心烦热、潮热盗汗,”(同日,s2);“无畏寒肢冷”(同日,s2);' . '“无咳嗽咯痰、胸痛心悸、浮肿或腹泻”(同日,s2);“无明显急性感染诱因”(同日,s2)。' . '腰酸耳鸣、固定刺痛为“未记录腰酸耳鸣或固定刺痛”(同日,s2),属于未知,不能作为阴性。'; factStateStreamCheck($pilotLedger, $sameDayUnknownParagraph, true, 'portable full question paragraph binds relative-date metadata before normalizing the identical unknown subject'); $portableWire = $dyspneaParagraph . "\r\n\r\n" . '**切诊:**汗出及四肢温度的触诊、腹诊、按诊未记录,待补。' . "\r\n\r\n" . $sameDayUnknownParagraph; $portableVisible = ''; $portableStream = new TcmClinicalReportStream($pilotLedger, static function (string $delta) use (&$portableVisible): bool { $portableVisible .= $delta; return true; }); $portablePushed = true; // Byte chunks deliberately split UTF-8 characters and annotation/CRLF tokens. foreach (str_split($portableWire, 7) as $chunk) { if (!$portableStream->push($chunk)) { $portablePushed = false; break; } } factStateExpect($portablePushed && $portableStream->finish() && !$portableStream->rejected(), 'all three portable paragraphs stream through split UTF-8 and CRLF byte boundaries'); factStateExpect($portableVisible === $portableWire, 'normalization cannot alter any accepted original byte including CRLF and a final undelimited paragraph'); foreach (['(同日,s2)', '(同日, s2)', '(同日,[s2])'] as $annotation) { factStateStreamCheck($pilotLedger, '“无恶寒发热”(2026-10-23,s2);固定刺痛为“未记录固定刺痛”' . $annotation . ',属于未知。', true, 'a same-day citation inherits only the preceding verified calendar date: ' . $annotation); } factStateStreamCheck($pilotLedger, '“无恶寒发热”(s2);固定刺痛为“未记录固定刺痛”(同日,s2)。', false, 'same-day metadata without a preceding full calendar date is rejected', 'SOURCE_ANNOTATION_INVALID', '未记录固定刺痛'); factStateStreamCheck($pilotLedger, '“无恶寒发热”(2026-10-23,s2);固定刺痛为“未记录固定刺痛”(同日,s999)。', false, 'a relative date cannot make an unknown source ID trusted', 'SOURCE_ANNOTATION_INVALID', '未记录固定刺痛'); factStateStreamCheck($pilotLedger, '“无恶寒发热”(2026-10-23,s2);固定刺痛为“未记录固定刺痛”(同日,患者有心悸)。', false, 'clinical text inside a same-day parenthesis remains a patient assertion', 'NEGATIVE_TO_POSITIVE', '有心悸'); factStateStreamCheck($pilotLedger, '“无恶寒发热”(2026-10-23,s2);固定刺痛为“未记录固定刺痛”(同日,[s2,患者无固定刺痛])。', false, 'nested brackets in relative-date metadata cannot strip a clinical negative', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($pilotLedger, '“无恶寒发热”(2026-10-23,s2);心悸为“未记录心悸”(同日,s2),属于未知。', false, 'valid relative-date metadata does not erase a known negative', 'NEGATIVE_TO_UNKNOWN', '未记录心悸'); factStateStreamCheck($pilotLedger, '“无恶寒发热”(2026-10-23,s2);口干为“未记录口干”(同日,s2)。', false, 'valid relative-date metadata does not erase a known positive', 'POSITIVE_TO_UNKNOWN', '未记录口干'); factStateStreamCheck($pilotLedger, '“无恶寒发热”(2026-10-23,s2);患者有固定刺痛为“未记录固定刺痛”(同日,s2)。', false, 'valid relative-date metadata does not exempt a positive copular subject', 'UNKNOWN_TO_POSITIVE', '有固定刺痛'); factStateStreamCheck($pilotLedger, '“无恶寒发热”(2026-10-23,s2);腰酸耳鸣为“未记录固定刺痛”(同日,s2)。', false, 'relative-date parsing does not bind different unknown subjects', 'UNKNOWN_TO_POSITIVE', '腰酸耳鸣'); factStateStreamCheck($pilotLedger, '“无恶寒发热”(2026-10-23,s2);固定刺痛为“未记录固定刺痛”(同日,s2),患者无固定刺痛。', false, 'a later actual absence stays outside the same-day unknown explanation', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($pilotLedger, '“无恶寒发热”(2026-10-23,s2)。' . "\n\n" . '固定刺痛为“未记录固定刺痛”(同日,s2)。', false, 'an ambiguous same-day antecedent cannot carry across paragraph boundaries', 'SOURCE_ANNOTATION_INVALID', '未记录固定刺痛'); factStateStreamCheck($dated, '运动记录为“无胸痛或晕厥”(2026-10-22,' . $historicalSource['id'] . ');运动记录为“无胸痛或晕厥”(同日,' . $historicalSource['id'] . ')。', true, 'a relative historical date remains attached to the correctly dated event'); factStateStreamCheck($dated, '运动记录为“无胸痛或晕厥”(2026-10-22,' . $historicalSource['id'] . ');本次耳鸣未记录(同日,s1)。', false, 'a same-day annotation cannot move a historical event onto a current source date', 'SOURCE_ANNOTATION_INVALID', '未记录'); factStateStreamCheck($dated, '“无胸痛心悸”(2026-10-23,s1);运动记录为“无胸痛或晕厥”(同日,' . $historicalSource['id'] . ')。', false, 'a same-day annotation cannot move a historical source onto the current day', 'SOURCE_ANNOTATION_INVALID', '无胸痛或晕厥'); factStateStreamCheck($dated, '运动记录为“无胸痛或晕厥”(2026-10-22,' . $historicalSource['id'] . ');运动记录为“无胸痛”(同日,' . $historicalSource['id'] . ')。', false, 'a valid relative historical date still requires a complete original negative scope', 'ATTRIBUTED_SOURCE_QUOTE_INVALID', '无胸痛'); foreach (['无急性感染诱因。', '无五心烦热、潮热盗汗。', '无烦渴。'] as $broadened) { factStateStreamCheck($pilotLedger, $broadened, false, 'the real v25 degree or compound-scope deletion remains rejected: ' . $broadened, 'NEGATION_SCOPE_WIDENED', $broadened); } // v27: report structure, location references and file-availability explanations // retain their roles. These complete literals need no retained local artifacts. $pulseLocationParagraph = '**切诊:**脉诊结果见下方独立脉象行;腹诊、肌肤温度及按诊资料未记录。'; $unknownIndexParagraph = '**显式未知/待补:**1.“未记录腰酸耳鸣或固定刺痛”(s2,2026-10-23),二者均保持未知;' . '还需补采体位性变化、汗出时冷热感、饮水总量、口渴昼夜规律及完整腹诊。' . '附件缺失只表示不能图像复核,不代表舌体不存在其他异常。'; $attachmentRoleParagraph = "**明确未知/待补:** \n1. “未记录腰酸耳鸣或固定刺痛”(s2,2026-10-23),两项均保持未知。 \n" . '2. “无附件”(s11,2026-10-23),表示没有可复核图像或报告,并非患者无异常。' . '氧饱和度、腰围及部分系统查体亦未记录。'; foreach ([$pulseLocationParagraph, $unknownIndexParagraph, $attachmentRoleParagraph] as $index => $paragraph) { factStateStreamCheck($pilotLedger, $paragraph, true, 'portable complete v26 rejected unit retains its report role: ' . $index); } foreach (['脉诊结果见下方独立脉象行。', '切诊:脉象结果见下文脉象行。'] as $reference) { factStateStreamCheck($pilotLedger, $reference, true, 'a closed location reference does not invent pulse 结 from 结果'); factStateStreamCheck($examUnknown, $reference, true, 'a location reference supplies no observed value even when the pulse field is unknown'); } factStateStreamCheck($pilotLedger, '脉诊结果见下方独立脉象行,脉细弱,按之无力。', true, 'a location reference can precede a faithful actual pulse value'); foreach (['脉结。', '脉滑。', '脉诊结果见下方独立脉象行,患者脉滑。', '患者脉滑,脉诊结果见下方独立脉象行。', '脉诊结果见下方独立脉象行且患者脉滑。'] as $observation) { factStateStreamCheck($pilotLedger, $observation, false, 'actual pulse findings remain visible beside or outside the closed reference', 'PULSE_FEATURE_NOT_IN_CURRENT_SOURCE', str_contains($observation, '脉滑') ? '脉滑' : '脉结'); } factStateStreamCheck($pilotLedger, '脉诊结果见下方独立脉象行,患者无固定刺痛。', false, 'a reference cannot exempt an actual patient negative tail', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($pilotLedger, '患者有心悸,脉诊结果见下方独立脉象行。', false, 'a reference cannot exempt an earlier actual patient positive', 'NEGATIVE_TO_POSITIVE', '有心悸'); factStateStreamCheck($pilotLedger, '脉诊结果见下方独立脉象行,口干未记录。', false, 'a reference cannot erase an already recorded positive', 'POSITIVE_TO_UNKNOWN', '未记录'); $numberedLedger = TcmClinicalFactStateGuard::build(['present_illness' => 'eGFR 96mL/min/1.73m²;口干;无胸痛;未记录耳鸣。']); foreach (['显式未知/待补:1.未记录耳鸣。', '明确未知/待补:1. 未记录耳鸣。', "明确未知/待补:\n1. 未记录耳鸣。", '未知/待补:2、耳鸣未记录。'] as $heading) { factStateStreamCheck($numberedLedger, $heading, true, 'structural heading and list number cannot become a fragment of a recorded lab value'); } factStateStreamCheck($numberedLedger, '显式未知/待补:1.口干未记录。', false, 'number removal retains an actual known positive to unknown', 'POSITIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($numberedLedger, '明确未知/待补:1.胸痛未记录。', false, 'number removal retains an actual known negative to unknown', 'NEGATIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($numberedLedger, '显式未知/待补:1.患者有耳鸣。', false, 'an uncertainty heading cannot exempt a positive unknown symptom', 'UNKNOWN_TO_POSITIVE', '有耳鸣'); factStateStreamCheck($numberedLedger, '1.73m²未记录。', false, 'a real decimal topic is not consumed as a numbered-list marker', 'POSITIVE_TO_UNKNOWN', '未记录'); $attachmentExplanation = '无附件,表示没有可复核图像或报告,并非患者无异常'; $recheckExplanation = '附件缺失只表示不能图像复核,不代表舌体不存在其他异常'; foreach ([$attachmentExplanation, $recheckExplanation, '未提供舌照,仅表示没有可复核图片或报告,不代表本例没有其他异常'] as $metadata) { factStateStreamCheck($pilotLedger, $metadata . '。', true, 'a bounded metadata role does not assert the nested patient absence'); factStateStreamCheck($pilotLedger, $metadata . ',脉细弱。', true, 'a metadata explanation can coexist with a faithful observed pulse'); factStateStreamCheck($pilotLedger, $metadata . ',患者无固定刺痛。', false, 'metadata roles do not hide a following actual unknown-to-negative', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($pilotLedger, '患者有心悸,' . $metadata . '。', false, 'metadata roles do not hide an earlier actual negative-to-positive', 'NEGATIVE_TO_POSITIVE', '有心悸'); factStateStreamCheck($pilotLedger, $metadata . ',口干未记录。', false, 'metadata roles do not erase a later known positive', 'POSITIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($pilotLedger, '心悸未记录,' . $metadata . '。', false, 'metadata roles do not erase an earlier known negative', 'NEGATIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($pilotLedger, $metadata . ',舌质红绛。', false, 'metadata roles do not hide a new tongue feature', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '舌质红绛'); factStateStreamCheck($pilotLedger, $metadata . ',患者脉滑。', false, 'metadata roles do not hide a new pulse feature', 'PULSE_FEATURE_NOT_IN_CURRENT_SOURCE', '脉滑'); } factStateStreamCheck($pilotLedger, '无附件,表示没有可复核图像或报告,患者无异常。', false, 'an actual broad patient absence is not the closed logical limitation', 'NEGATION_SCOPE_WIDENED', '无异常'); factStateStreamCheck($pilotLedger, $attachmentExplanation . '且患者有心悸。', false, 'an unbounded clinical suffix cannot fit the metadata explanation', 'NEGATIVE_TO_POSITIVE', '有心悸'); factStateStreamCheck($pilotLedger, '无附件(s999),表示没有可复核图像或报告,并非患者无异常。', false, 'metadata roles cannot make an invalid citation trusted', 'SOURCE_ANNOTATION_INVALID', '无附件'); $nullAliasCurrent = ['id' => 7301, 'diagnosis_date' => '2026-10-23', 'chief_complaint' => '乏力', 'present_illness' => null, 'tongue' => null, 'pulse' => null, 'current_medications' => null, 'allergy_history' => 0, 'allergy_history_text' => '未记录', 'family_history' => 0, 'family_history_text' => '未记录', 'surgery_history' => 0, 'surgery_history_text' => '未记录', 'trauma_history' => 0, 'trauma_history_text' => '未记录', 'pregnancy_history' => 0, 'pregnancy_history_text' => '未记录']; $nullAliasSubmission = AiRequiredSource::submission(['diagnoses' => [$nullAliasCurrent]]); $nullAliasLedger = TcmClinicalFactStateGuard::build($nullAliasCurrent, $nullAliasSubmission); factStateExpect(!array_filter($nullAliasLedger['sources'], static fn (array $source): bool => in_array($source['field'], ['present_illness', 'tongue', 'pulse', 'current_medications'], true)), 'complete-source nested nulls do not fabricate source findings'); foreach (['allergy_history' => '过敏史', 'family_history' => '家族史', 'surgery_history' => '手术史', 'trauma_history' => '外伤史', 'pregnancy_history' => '孕产史'] as $field => $label) { factStateExpect($nullAliasLedger['field_states'][$field]['state'] === 'unknown', 'legacy zero and its explicit unknown alias retain an unknown field state: ' . $field); factStateStreamCheck($nullAliasLedger, $label . ':未记录。', true, 'complete-source explicit unknown alias remains usable: ' . $field); factStateStreamCheck($nullAliasLedger, $label . ':无。', false, 'complete-source zero/null representation cannot authorize a patient denial: ' . $field, 'UNKNOWN_TO_NEGATIVE', '无'); } factStateStreamCheck($nullAliasLedger, '舌象未记录,脉象未记录。', true, 'null current examinations remain unknown after complete-source projection'); factStateStreamCheck($nullAliasLedger, '脉滑。', false, 'a null current examination never supplies a new pulse feature', 'PULSE_FEATURE_NOT_IN_CURRENT_SOURCE', '脉滑'); $explicitAliasCurrent = array_replace($nullAliasCurrent, ['allergy_history_text' => '本次询问否认药物过敏史']); $explicitAliasLedger = TcmClinicalFactStateGuard::build($explicitAliasCurrent, AiRequiredSource::submission(['diagnoses' => [$explicitAliasCurrent]])); factStateStreamCheck($explicitAliasLedger, '本次询问否认药物过敏史。', true, 'an explicit source denial remains valid beside a raw legacy zero'); factStateStreamCheck($explicitAliasLedger, '过敏史未记录。', false, 'an explicit text denial cannot be erased by the legacy representation', 'NEGATIVE_TO_UNKNOWN', '未记录'); $v27Wire = $pulseLocationParagraph . "\r\n\r\n" . $unknownIndexParagraph . "\r\n\r\n" . $attachmentRoleParagraph; foreach ([1, 7, 19] as $byteSize) { $v27Visible = ''; $v27Stream = new TcmClinicalReportStream($pilotLedger, static function (string $delta) use (&$v27Visible): bool { $v27Visible .= $delta; return true; }); $v27Pushed = true; foreach (str_split($v27Wire, $byteSize) as $chunk) { if (!$v27Stream->push($chunk)) { $v27Pushed = false; break; } } factStateExpect($v27Pushed && $v27Stream->finish() && !$v27Stream->rejected(), 'v27 complete portable units survive UTF-8/metadata/CRLF boundaries at byte size ' . $byteSize); factStateExpect($v27Visible === $v27Wire, 'v27 role normalization preserves every accepted original byte at byte size ' . $byteSize); } // v29: a centralized date has only a closed, same-paragraph negative-list role. $datedNegativeList = "已记录阴性(2026-10-23):\n" . "1.“无恶寒发热,”(s2)\n2.“无高热烦渴、汗出淋漓或意识异常”(s2)\n" . "3.“无明显五心烦热、潮热盗汗,”(s2)\n4.“无畏寒肢冷”(s2)\n" . "5.“无咳嗽咯痰、胸痛心悸、浮肿或腹泻”(s2)\n6.“无明显急性感染诱因”(s2)\n" . "7.“无喘促及异常气味”(s3)\n8.“无已知冠心病、心衰或慢性肾病”(s4)\n" . "9.“无已知药物过敏史”(s5)\n10.“未使用胰岛素或中药”(s6)"; $bloodCombination = '血分方面,面色少华、脉细可供血虚鉴别,但缺乏头晕、唇甲色淡等记录,现有证据不足以确认血虚;' . '固定刺痛为未知,舌无紫暗瘀斑,故血瘀缺少阳性支持。'; factStateStreamCheck($pilotLedger, $datedNegativeList, true, 'a portable complete centralized-date list validates each original quote and source date'); factStateStreamCheck($pilotLedger, $datedNegativeList . "\n另据2026-10-22运动记录:“无胸痛或晕厥。”(原资料未提供source_id)", true, 'the list date stops before a separately dated historical observation'); foreach ([ str_replace('2026-10-23', '2026-10-22', $datedNegativeList), str_replace('(s3)', '(s999)', $datedNegativeList), str_replace('7.“无喘促及异常气味”(s3)', '7.“无喘促”(s3)', $datedNegativeList), str_replace('7.“无喘促及异常气味”(s3)', '7.“无喘促及异常气味”', $datedNegativeList), str_replace('已记录阴性(2026-10-23):', '已记录阴性(2026-10-23,患者无固定刺痛):', $datedNegativeList), "已记录阴性(2026-10-23):\n\n1.“无恶寒发热,”(s2)", ] as $invalidList) { factStateStreamCheck($pilotLedger, $invalidList, false, 'a date header cannot bypass source IDs, dates, complete quotes, closed grammar or paragraph boundaries'); } factStateStreamCheck($pilotLedger, $datedNegativeList . "\n患者无固定刺痛。", false, 'a dated list cannot authorize a following current unknown-to-negative', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($pilotLedger, $datedNegativeList . "\n心悸未记录。", false, 'a dated list cannot erase a known source negative', 'NEGATIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($pilotLedger, $datedNegativeList . "\n口干未记录。", false, 'a dated list cannot erase a known source positive', 'POSITIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($pilotLedger, $datedNegativeList . "\n固定刺痛未记录(同日,s2)。", false, 'a centralized heading never seeds same-day metadata outside its list', 'SOURCE_ANNOTATION_INVALID', '未记录'); $oldHeaderSource = array_values(array_filter($dated['sources'], static fn (array $row): bool => str_starts_with($row['path'], 'daily_tracking.exercise.')))[0]; $oldDateList = "已记录阴性(2026-10-22):\n1.“无胸痛或晕厥”(" . $oldHeaderSource['id'] . ')'; factStateStreamCheck($dated, $oldDateList, true, 'a valid dated historical list remains an explicitly dated historical quotation'); factStateStreamCheck($dated, str_replace('2026-10-22', '2026-10-23', $oldDateList), false, 'a current header date cannot promote an older source', 'SOURCE_ANNOTATION_INVALID', '无胸痛'); factStateStreamCheck($dated, $oldDateList . "\n本次无耳鸣。", false, 'a historical list date cannot leak to a later explicitly current assertion', 'UNKNOWN_TO_NEGATIVE', '无耳鸣'); factStateStreamCheck($pilotLedger, $bloodCombination, true, 'the complete portable blood paragraph combines two exact negative features from one current tongue source'); factStateStreamCheck($pilotLedger, '舌无紫暗瘀斑。', true, 'a negative tongue combination preserves 紫暗 and 瘀斑 without inventing broad 舌紫'); foreach ([ TcmClinicalFactStateGuard::build(['tongue' => '无明显紫暗、瘀斑。']), TcmClinicalFactStateGuard::build(['tongue' => '无紫暗。', 'tongue_coating' => '无瘀斑。']), TcmClinicalFactStateGuard::build(['tongue' => '舌紫暗,无瘀斑。']), ] as $unsupportedCombination) { factStateStreamCheck($unsupportedCombination, '舌无紫暗瘀斑。', false, 'the combination requires both complete unqualified negatives from the same current tongue source', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '舌无紫暗瘀斑'); } factStateStreamCheck($pilotLedger, '舌无紫暗瘀斑,患者有固定刺痛。', false, 'a supported tongue combination cannot authorize a new positive tail', 'UNKNOWN_TO_POSITIVE', '有固定刺痛'); factStateStreamCheck($pilotLedger, '舌无紫暗瘀斑,舌质红绛。', false, 'a supported tongue combination cannot hide a new tongue feature', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '舌质红绛'); factStateStreamCheck($pilotLedger, '舌无紫暗瘀斑,脉滑。', false, 'a supported tongue combination cannot hide a new pulse feature', 'PULSE_FEATURE_NOT_IN_CURRENT_SOURCE', '脉滑'); factStateStreamCheck($pilotLedger, '无急性感染诱因。', false, 'the actual Qwen deletion of 明显 remains rejected', 'NEGATION_SCOPE_WIDENED', '无急性感染诱因'); $v29Wire = str_replace("\n", "\r\n", $datedNegativeList) . "\r\n\r\n" . $bloodCombination; $v29Visible = ''; $v29Stream = new TcmClinicalReportStream($pilotLedger, static function (string $delta) use (&$v29Visible): bool { $v29Visible .= $delta; return true; }); $v29Pushed = true; foreach (str_split($v29Wire, 7) as $chunk) { if (!$v29Stream->push($chunk)) { $v29Pushed = false; break; } } factStateExpect($v29Pushed && $v29Stream->finish() && !$v29Stream->rejected(), 'both portable v29 roles survive split UTF-8, source metadata and CRLF boundaries'); factStateExpect($v29Visible === $v29Wire, 'v29 validation-only normalization preserves all original accepted bytes'); // v30: the closed “未知项:” label is report structure, including before item 1. $unknownItemSingle = '未知项:1.“未记录腰酸耳鸣或固定刺痛”(s2,2026-10-23);' . '2.腹诊、体位性血压、氧饱和度、近期体重变化及完整饮食饮水量未采集或未记录;相关空栏保持未知。'; $unknownItemConcurrent = '未知项:1.“未记录腰酸耳鸣或固定刺痛”(s2,2026-10-23);' . '2.婚姻、地区、腰围、血氧及体重变化等为空栏,保持未知;' . '3.无舌图、图片备注、文件报告及视频附件,不能据此判断相应体征或检查结果。'; foreach ([$unknownItemSingle, $unknownItemConcurrent] as $paragraph) { factStateStreamCheck($pilotLedger, $paragraph, true, 'the portable complete v29 unknown-items paragraph cannot turn item 1 into an eGFR data gap'); } factStateStreamCheck($numberedLedger, '未知项:1.未记录耳鸣。', true, 'a closed unknown-items heading binds only its following item structure'); foreach (['未知项:1.eGFR 96mL/min/1.73m²未记录。', '未知项:2.未记录eGFR 96mL/min/1.73m²。', '未知项:1.73m²未记录。'] as $erasedLab) { factStateStreamCheck($numberedLedger, $erasedLab, false, 'Latin lab items and true decimal topics retain known-to-unknown checks', 'POSITIVE_TO_UNKNOWN', '未记录'); } factStateStreamCheck($pilotLedger, '未知项:1.eGFR 96mL/min/1.73m²未记录(2026-10-23,s23)。', false, 'the actual dated s23 lab cannot be erased by an unknown-items heading', 'POSITIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($numberedLedger, '未知项:1.口干未记录。', false, 'an unknown-items heading cannot erase a recorded clinical positive', 'POSITIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($numberedLedger, '未知项:1.胸痛未记录。', false, 'an unknown-items heading cannot erase a recorded clinical negative', 'NEGATIVE_TO_UNKNOWN', '未记录'); foreach (['未知项:1.患者有耳鸣。', '未知项:1..患者有耳鸣。', '未知项1.患者有耳鸣。'] as $actualClinicalItem) { factStateStreamCheck($numberedLedger, $actualClinicalItem, false, 'valid or malformed numeric/clinical list syntax never exempts an actual observation', 'UNKNOWN_TO_POSITIVE', '有耳鸣'); } factStateStreamCheck($numberedLedger, '未知项:1.无耳鸣。', false, 'a closed unknown-items heading cannot authorize an actual negative', 'UNKNOWN_TO_NEGATIVE', '无耳鸣'); $v30Wire = $unknownItemSingle . "\r\n\r\n" . $unknownItemConcurrent; $v30Visible = ''; $v30Stream = new TcmClinicalReportStream($pilotLedger, static function (string $delta) use (&$v30Visible): bool { $v30Visible .= $delta; return true; }); $v30Pushed = true; foreach (str_split($v30Wire, 7) as $chunk) { if (!$v30Stream->push($chunk)) { $v30Pushed = false; break; } } factStateExpect($v30Pushed && $v30Stream->finish() && !$v30Stream->rejected(), 'both complete v30 portable units stream through split UTF-8 and CRLF boundaries'); factStateExpect($v30Visible === $v30Wire, 'v30 heading normalization preserves every accepted original input byte'); // v31: three proven discourse roles, with observed assertions kept outside them. $evidenceNounParagraph = '气方面以气虚为主:乏力、动则气短、自汗、语声低微与无力脉相互印证,并见活动后加重,提示推动、固摄功能不足。' . '血方面仅有面色少华、脉细可作血虚鉴别线索,但未记录头晕、唇甲色泽等,现有证据不足以确认血虚;亦无本次阳性资料支持血瘀。'; $upperPulseParagraph = "望诊:神疲,面色少华。身高170cm,体重65kg,BMI 22.5kg/m²。体温36.5℃,血压124/76mmHg。 \n" . "闻诊:语声低微。 \n问诊:乏力、动则气短、自汗、口干3个月;轻微活动后气短,白天自汗且活动后加重,口干欲饮、少量频饮。" . "食欲一般,大便稍干、每日1次;小便量较以往略多,夜尿1次;睡眠一般。2026-10-22步行20分钟后乏力、汗出较明显。 \n" . "舌象:本次面诊记录:舌质淡红,舌面少津,苔少;无黄腻苔、紫暗或瘀斑。(s9) \n" . "脉象:本次医师切诊:脉细弱,按之无力;不浮、不滑、不紧。(s10) \n切诊:脉诊结果见上列脉象;其他切诊体征未记录。"; $reviewQuestionParagraph = '适用前须核实:复核自汗与气短是否持续呈活动后加重,有无新发感染、胸闷胸痛、心悸、晕厥或气促加剧;' . '补采完整饮水量、尿量、体重变化、腹诊及腰膝耳鸣等资料。若出现急性发热、明显实热、痰湿壅盛或其他实邪表现,应重新辨证,不能机械沿用本方向。'; foreach ([$evidenceNounParagraph, $upperPulseParagraph, $reviewQuestionParagraph] as $paragraph) { factStateStreamCheck($pilotLedger, $paragraph, true, 'a complete portable v30 rejected unit retains its evidence, reference or review-question role'); } factStateStreamCheck($pilotLedger, '亦无本次阳性资料支持血瘀。', true, 'a closed evidence noun is not an observed patient negative'); foreach (['亦无本次阳性资料支持血瘀,患者无固定刺痛。', '患者无固定刺痛,亦无本次阳性资料支持血瘀。'] as $mixedEvidence) { factStateStreamCheck($pilotLedger, $mixedEvidence, false, 'an evidence noun cannot hide an actual negative before or after it', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); } factStateStreamCheck($pilotLedger, '亦无本次阳性资料支持血瘀且患者有心悸。', false, 'an evidence noun has no wildcard slot for an actual positive tail', 'NEGATIVE_TO_POSITIVE', '有心悸'); foreach (['切诊:脉诊结果见上列脉象。', '脉象结果见上述独立脉象行。', '脉诊结果见下列脉象。'] as $locationReference) { factStateStreamCheck($pilotLedger, $locationReference, true, 'upper and lower location references contain no observed pulse value'); } factStateStreamCheck($pilotLedger, '切诊:脉诊结果见上列脉象,脉结。', false, 'a result reference does not exempt an actual pulse 结', 'PULSE_FEATURE_NOT_IN_CURRENT_SOURCE', '脉结'); factStateStreamCheck($pilotLedger, '脉诊结果见上列脉象且脉滑。', false, 'an unbounded assertion tail does not fit the pulse-reference role', 'PULSE_FEATURE_NOT_IN_CURRENT_SOURCE', '脉滑'); factStateStreamCheck($pilotLedger, '患者无固定刺痛,脉诊结果见上列脉象。', false, 'an upper reference does not hide an earlier actual patient negative', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($pilotLedger, '脉诊结果见上列脉象,口干未记录。', false, 'a result reference cannot erase a recorded positive', 'POSITIVE_TO_UNKNOWN', '未记录'); $reviewQuestion = '适用前须核实:复核自汗与气短是否持续呈活动后加重,有无新发感染、胸闷胸痛、心悸、晕厥或气促加剧'; factStateStreamCheck($pilotLedger, '适用前须核实:有无心悸或固定刺痛。', true, 'an anchored yes/no inquiry does not assert either source polarity'); foreach ([$reviewQuestion . ',患者无固定刺痛。', '患者无固定刺痛,' . $reviewQuestion . '。', $reviewQuestion . '且患者无固定刺痛。', '适用前须核实:复核无固定刺痛。'] as $mixedQuestion) { factStateStreamCheck($pilotLedger, $mixedQuestion, false, 'a review request cannot hide an actual negative prefix, suffix or non-question statement', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); } foreach ([$reviewQuestion . ',患者有心悸。', $reviewQuestion . '且患者有心悸。', '适用前须核实:患者有心悸,复核自汗是否持续。'] as $mixedQuestion) { factStateStreamCheck($pilotLedger, $mixedQuestion, false, 'a request frame cannot lend its generic collection exemption to an actual positive', 'NEGATIVE_TO_POSITIVE', '有心悸'); } factStateStreamCheck($pilotLedger, $reviewQuestion . ',心悸未记录。', false, 'a review question cannot erase a recorded negative tail', 'NEGATIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($pilotLedger, '无固定刺痛。', false, 'the actual Qwen unknown-to-negative error remains rejected', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($pilotLedger, '无五心烦热。', false, 'the actual Qwen degree-qualifier deletion remains rejected', 'NEGATION_SCOPE_WIDENED', '无五心烦热'); $v31Wire = $evidenceNounParagraph . "\r\n\r\n" . str_replace("\n", "\r\n", $upperPulseParagraph) . "\r\n\r\n" . $reviewQuestionParagraph; $v31Visible = ''; $v31Stream = new TcmClinicalReportStream($pilotLedger, static function (string $delta) use (&$v31Visible): bool { $v31Visible .= $delta; return true; }); $v31Pushed = true; foreach (str_split($v31Wire, 7) as $chunk) { if (!$v31Stream->push($chunk)) { $v31Pushed = false; break; } } factStateExpect($v31Pushed && $v31Stream->finish() && !$v31Stream->rejected(), 'all three portable v31 roles survive split UTF-8, source annotation and CRLF boundaries'); factStateExpect($v31Visible === $v31Wire, 'v31 discourse-role handling preserves every accepted original byte'); // v32: grammatical data objects, bounded quantity predicates and review actions. // These complete units are synthetic literals, independent of retained artifacts. $missingRecordParagraph = '血方面仅有面色少华、脉细可供鉴别,尚不足以确认血虚;缺乏固定刺痛资料,亦不能确认血瘀。待补依据:见第一节未知第1项(s2)。'; $quantityParagraph = '气分以气虚为主要判断:乏力、活动后气短和汗出加重,与语声低微、脉弱无力相互印证,并见卫外不固、腠理失摄倾向。' . '津液方面为津液偏少:口干欲饮而每次不多、大便稍干、舌面少津苔少;自汗及小便较前略多可能增加津液耗失,但不能仅据尿量变化确立肾阴虚或消渴下消。'; $safetyReviewParagraph = '禁忌与安全复核:虽第一节阴性第9项(s5)记录本次询问情况,新用药仍须逐味复核过敏、既往不良反应、肝肾功能、合并药物及制剂含糖辅料。' . '2026-10-20 ALT 22U/L、肌酐78μmol/L、eGFR 96mL/min/1.73m²、尿ACR 12mg/g可作当前安全基线,不代表后续无需监测。' . '若复核发现邪实未解、痰湿壅盛、中满纳差等不适用条件,不宜机械采用本方向。'; foreach ([$missingRecordParagraph, $quantityParagraph, $safetyReviewParagraph] as $paragraph) { factStateStreamCheck($pilotLedger, $paragraph, true, 'a complete v32 portable unit keeps its missing-record, quantity or review-action role'); } $gapLedger = TcmClinicalFactStateGuard::build([ 'diagnosis_date' => '2026-10-23', 'present_illness' => '口干;无心悸;未记录耳鸣、固定刺痛或胰岛素。', 'tongue' => '舌质淡红,舌面少津,苔少。', 'pulse' => '脉细弱。', ]); foreach (['缺乏固定刺痛资料。', '缺少耳鸣记录。', '无耳鸣资料。', '未见耳鸣记录。', '缺少固定刺痛及耳鸣的专门文字描述。', '缺少舌裂纹资料。'] as $gapText) { factStateStreamCheck($gapLedger, $gapText, true, 'a closed missing-data object retains unknown rather than patient-positive or patient-negative state'); } foreach (['缺乏口干资料。', '缺少口干记录。', '没有口干专门描述。', '缺少口干及耳鸣记录。'] as $erasedPositive) { factStateStreamCheck($gapLedger, $erasedPositive, false, 'missing-record metadata cannot erase a known same-time positive', 'POSITIVE_TO_UNKNOWN', '口干'); } foreach (['缺少心悸记录。', '无心悸资料。'] as $erasedNegative) { factStateStreamCheck($gapLedger, $erasedNegative, false, 'missing-record metadata cannot erase a known same-time negative', 'NEGATIVE_TO_UNKNOWN', '心悸'); } factStateStreamCheck($gapLedger, '舌象:缺少资料。', false, 'a field label supplies the missing record subject', 'RECORDED_TO_UNKNOWN', '缺少资料'); factStateStreamCheck($gapLedger, "脉象:\n\n缺乏资料。", false, 'a carried recorded field cannot become an empty data gap across paragraphs', 'RECORDED_TO_UNKNOWN', '缺乏资料'); factStateStreamCheck($examUnknown, '舌象:缺少资料。', true, 'the same labeled data gap is faithful when the examination really is unknown'); factStateStreamCheck($examUnknown, '舌象:缺少资料,脉象未记录。', true, 'a comma-delimited field gap cannot turn the next unknown examination into an observed value'); factStateStreamCheck($examUnknown, '舌象:缺少资料,脉滑。', false, 'a comma-delimited field gap cannot authorize an actual pulse value', 'PULSE_FEATURE_NOT_IN_CURRENT_SOURCE', '脉滑'); factStateStreamCheck($gapLedger, '舌象:缺少资料,脉细弱。', false, 'a later grounded pulse cannot hide erasure of the recorded tongue field', 'RECORDED_TO_UNKNOWN', '缺少资料'); factStateStreamCheck($gapLedger, '缺少口干记录(2026-10-23,s1)。', false, 'valid source metadata cannot authorize erasing its actual positive', 'POSITIVE_TO_UNKNOWN', '口干'); factStateStreamCheck($gapLedger, '缺少耳鸣记录(2026-10-22,s1)。', false, 'a data-gap role cannot make an inconsistent source date valid', 'SOURCE_ANNOTATION_INVALID', '缺少耳鸣'); factStateStreamCheck($gapLedger, '缺乏胰岛素。', false, 'a deficiency without a record head noun remains an actual clinical proposition', 'UNKNOWN_TO_POSITIVE', '缺乏胰岛素'); $sourceGapLedger = TcmClinicalFactStateGuard::build(['present_illness' => '缺少耳鸣记录;缺乏胰岛素。']); factStateStreamCheck($sourceGapLedger, '无耳鸣。', false, 'source-side data-gap grammar also preserves unknown', 'UNKNOWN_TO_NEGATIVE', '无耳鸣'); factStateStreamCheck($sourceGapLedger, '胰岛素未记录。', false, 'source-side deficiency is not indexed as missing-record metadata', 'POSITIVE_TO_UNKNOWN', '未记录'); foreach (['缺少耳鸣记录,患者有耳鸣。', '缺少耳鸣资料且患者有耳鸣。'] as $mixedGap) { factStateStreamCheck($gapLedger, $mixedGap, false, 'a record object cannot hide an adjacent actual positive', 'UNKNOWN_TO_POSITIVE', '有耳鸣'); } factStateStreamCheck($gapLedger, '患者无固定刺痛,缺少耳鸣记录。', false, 'a record object cannot hide an earlier actual negative', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($gapLedger, '缺少患者无耳鸣记录。', false, 'an observed negative cannot be embedded inside a neutral data object', 'UNKNOWN_TO_NEGATIVE', '无耳鸣'); factStateStreamCheck($gapLedger, '缺少耳鸣记录,患者有心悸。', false, 'a missing-data clause cannot reverse a different recorded negative', 'NEGATIVE_TO_POSITIVE', '有心悸'); factStateStreamCheck($pilotLedger, '口干欲饮而每次不多、大便稍干、舌面少津苔少。', true, 'a closed quantity predicate cannot carry its negation into later positive list items'); factStateStreamCheck($pilotLedger, '饮水量不多,舌面少津,苔少;脉细弱。', true, 'an explicit measure subject has the same quantity boundary as an iteration subject'); factStateStreamCheck($pilotLedger, '每次不多、大便稍干、舌质红绛。', false, 'quantity scoping does not exempt a later invented tongue feature', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '舌质红绛'); factStateStreamCheck($pilotLedger, '每次不多、脉滑。', false, 'quantity scoping does not exempt a later invented pulse feature', 'PULSE_FEATURE_NOT_IN_CURRENT_SOURCE', '脉滑'); factStateStreamCheck($pilotLedger, '每餐不少,患者有心悸。', false, 'a bounded quantity does not exempt a later explicit clinical positive', 'NEGATIVE_TO_POSITIVE', '有心悸'); factStateStreamCheck($pilotLedger, '每次不多,患者无固定刺痛。', false, 'a bounded quantity does not exempt a later explicit unknown-to-negative error', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($pilotLedger, '每次不多、无五心烦热。', false, 'a bounded quantity does not permit deleting a source degree qualifier', 'NEGATION_SCOPE_WIDENED', '无五心烦热'); factStateStreamCheck($pilotLedger, '每次不多,口干未记录。', false, 'a bounded quantity does not authorize a later known-to-unknown error', 'POSITIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($gapLedger, '耳鸣每次不多。', false, 'a quantity predicate still leaves its own clinical subject visible', 'UNKNOWN_TO_POSITIVE', '耳鸣'); factStateStreamCheck($pilotLedger, '不多、大便稍干、舌面少津苔少。', false, 'an unbound negative has no invented quantity subject', 'POSITIVE_TO_NEGATIVE', '大便'); factStateStreamCheck($pilotLedger, '每次不多舌质红绛。', false, 'an unclosed quantity complement cannot absorb a new examination value', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '红绛'); $quantityUnknownLedger = TcmClinicalFactStateGuard::build(['present_illness' => '饮水量未记录。']); factStateStreamCheck($quantityUnknownLedger, '饮水量不多。', false, 'a measured quantity cannot be invented from an unknown record', 'UNKNOWN_TO_POSITIVE', '饮水量'); $reviewAction = '新用药仍须逐味复核过敏、既往不良反应、肝肾功能'; factStateStreamCheck($pilotLedger, $reviewAction . '。', true, 'a modal collection action governs a neutral noun list without asserting its contents'); foreach (['患者无固定刺痛,' . $reviewAction . '。', $reviewAction . ',患者无固定刺痛。', '新用药仍须逐味复核无固定刺痛。'] as $mixedReviewAction) { factStateStreamCheck($pilotLedger, $mixedReviewAction, false, 'a review action cannot hide an actual negative prefix, suffix or object statement', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); } foreach (['患者有心悸,' . $reviewAction . '。', $reviewAction . ',患者有心悸。', $reviewAction . '且患者有心悸。', $reviewAction . '患者有心悸。', '新用药须复核患者有心悸。', '须复核患者有心悸。'] as $mixedReviewAction) { factStateStreamCheck($pilotLedger, $mixedReviewAction, false, 'a review action cannot hide an actual positive prefix, suffix or conjunction', 'NEGATIVE_TO_POSITIVE', '有心悸'); } factStateStreamCheck($pilotLedger, $reviewAction . ',心悸未记录。', false, 'a review action cannot erase a known negative', 'NEGATIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($pilotLedger, $reviewAction . '且舌质红绛。', false, 'a request object list stops before an assertion conjunction', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '红绛'); factStateStreamCheck($gapLedger, $reviewAction . '且患者有耳鸣。', false, 'a historical noun inside a request cannot redate a new current patient assertion', 'UNKNOWN_TO_POSITIVE', '有耳鸣'); factStateStreamCheck($pilotLedger, '患者无不良反应。', false, 'an adverse-reaction noun does not exempt an actual unsupported patient absence', 'NEGATIVE_NOT_IN_SOURCE', '无不良反应'); factStateStreamCheck($pilotLedger, '患者无药物过敏史。', false, 'the original known-allergy qualifier still cannot be deleted', 'NEGATION_SCOPE_WIDENED', '无药物过敏史'); $v32Wire = $missingRecordParagraph . "\r\n\r\n" . $quantityParagraph . "\r\n\r\n" . $safetyReviewParagraph; $v32Visible = ''; $v32Stream = new TcmClinicalReportStream($pilotLedger, static function (string $delta) use (&$v32Visible): bool { $v32Visible .= $delta; return true; }); $v32Pushed = true; foreach (str_split($v32Wire, 7) as $chunk) { if (!$v32Stream->push($chunk)) { $v32Pushed = false; break; } } factStateExpect($v32Pushed && $v32Stream->finish() && !$v32Stream->rejected(), 'all complete v32 roles survive split UTF-8, metadata and CRLF boundaries'); factStateExpect($v32Visible === $v32Wire, 'v32 scope analysis preserves every accepted original input byte'); // v33: a postfix gap modifies its closed record object, never a patient finding. // This portable literal includes the full rejected paragraph, without artifacts. $postfixRecordParagraph = '气方面,以气虚为主要病机:乏力、动则气短、自汗、语声低微及脉按之无力构成连续证据链,并提示气的推动、固摄功能不足。' . '血方面,面色少华、脉细可作为血分不足的鉴别线索,但缺少头晕、唇甲色淡等本例阳性记录,故血虚尚不能确证;' . '固定刺痛资料缺失,待补依据:见第一节未知第1项(s2)。'; factStateStreamCheck($pilotLedger, $postfixRecordParagraph, true, 'the complete v32 OpenAI paragraph keeps its postfix missing-record role'); foreach (['固定刺痛资料缺失。', '耳鸣记录缺失。', '耳鸣信息缺失。', '耳鸣描述缺失。', '耳鸣的专门文字描述缺失。', '耳鸣资料仍缺失。', '本次耳鸣记录尚缺失。', '耳鸣、固定刺痛资料缺失。', '耳鸣及固定刺痛资料缺失。', '舌裂纹资料缺失。', '耳鸣资料缺失,口干。', '耳鸣资料缺失,舌质淡红;脉细弱。'] as $postfixGap) { factStateStreamCheck($gapLedger, $postfixGap, true, 'a closed postfix record gap retains unknown and preserves adjoining grounded findings'); } foreach (['口干资料缺失。', '口干记录缺失。', '口干信息缺失。', '口干描述缺失。', '口干及耳鸣记录缺失。', '口干的专门文字描述缺失。'] as $postfixErasure) { factStateStreamCheck($gapLedger, $postfixErasure, false, 'a postfix record gap cannot erase a known positive', 'POSITIVE_TO_UNKNOWN', '口干'); } foreach (['心悸记录缺失。', '心悸资料缺失。', '耳鸣及心悸信息缺失。'] as $postfixErasure) { factStateStreamCheck($gapLedger, $postfixErasure, false, 'a postfix record gap cannot erase a known negative', 'NEGATIVE_TO_UNKNOWN', '心悸'); } factStateStreamCheck($gapLedger, '舌象:资料缺失。', false, 'a labeled postfix record gap still conflicts with a recorded examination', 'RECORDED_TO_UNKNOWN', '资料缺失'); factStateStreamCheck($gapLedger, "脉象:\n\n资料缺失。", false, 'a carried recorded examination cannot become a postfix data gap', 'RECORDED_TO_UNKNOWN', '资料缺失'); factStateStreamCheck($examUnknown, '舌象:资料缺失。', true, 'a labeled postfix record gap is faithful for an unknown examination'); factStateStreamCheck($examUnknown, '舌象:资料缺失,脉象未记录。', true, 'a postfix record gap cannot supply a value for the next unknown examination'); factStateStreamCheck($examUnknown, '舌象:资料缺失,脉滑。', false, 'a postfix record gap cannot authorize a new examination value', 'PULSE_FEATURE_NOT_IN_CURRENT_SOURCE', '脉滑'); foreach (['耳鸣资料缺失,患者有耳鸣。', '患者有耳鸣,耳鸣资料缺失。', '耳鸣资料缺失且患者有耳鸣。', '耳鸣资料缺失而患者有耳鸣。', '患者有耳鸣资料缺失。', '耳鸣资料缺失、固定刺痛。', '耳鸣资料缺失及固定刺痛。', '耳鸣资料缺失患者有耳鸣。'] as $mixedPostfixGap) { factStateStreamCheck($gapLedger, $mixedPostfixGap, false, 'a postfix record object cannot absorb an actual positive clause or list tail', 'UNKNOWN_TO_POSITIVE', '耳鸣'); } foreach (['耳鸣资料缺失,患者无固定刺痛。', '患者无固定刺痛,耳鸣资料缺失。', '耳鸣资料缺失且患者无固定刺痛。', '无固定刺痛资料缺失。'] as $mixedPostfixGap) { factStateStreamCheck($gapLedger, $mixedPostfixGap, false, 'a postfix record object cannot absorb an actual clinical negative', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); } factStateStreamCheck($gapLedger, '耳鸣资料缺失,患者有心悸。', false, 'a postfix data gap cannot reverse a different recorded negative', 'NEGATIVE_TO_POSITIVE', '有心悸'); factStateStreamCheck($pilotLedger, '固定刺痛资料缺失,口干未记录。', false, 'a postfix gap cannot erase an adjoining known positive', 'POSITIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($pilotLedger, '固定刺痛资料缺失,心悸未记录。', false, 'a postfix gap cannot erase an adjoining known negative', 'NEGATIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($pilotLedger, '固定刺痛资料缺失,无五心烦热。', false, 'a postfix gap cannot authorize deleting a degree qualifier', 'NEGATION_SCOPE_WIDENED', '无五心烦热'); factStateStreamCheck($pilotLedger, '固定刺痛资料缺失,舌质红绛。', false, 'a postfix gap cannot authorize an unsupported tongue feature', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '红绛'); factStateStreamCheck($pilotLedger, '固定刺痛资料缺失且脉滑。', false, 'a postfix gap cannot absorb a joined unsupported pulse feature', 'PULSE_FEATURE_NOT_IN_CURRENT_SOURCE', '脉滑'); factStateStreamCheck($gapLedger, '胰岛素缺失。', false, 'a deficiency without a record head noun stays a clinical proposition', 'UNKNOWN_TO_POSITIVE', '胰岛素'); factStateStreamCheck($gapLedger, '固定刺痛资料缺失(2026-10-22,s1)。', false, 'a postfix data gap cannot validate inconsistent source metadata', 'SOURCE_ANNOTATION_INVALID', '资料缺失'); $postfixSourceLedger = TcmClinicalFactStateGuard::build(['present_illness' => '耳鸣资料缺失;胰岛素缺失。']); factStateStreamCheck($postfixSourceLedger, '无耳鸣。', false, 'source-side postfix record grammar also retains unknown', 'UNKNOWN_TO_NEGATIVE', '无耳鸣'); factStateStreamCheck($postfixSourceLedger, '患者有耳鸣。', false, 'source-side postfix record grammar cannot become a positive', 'UNKNOWN_TO_POSITIVE', '有耳鸣'); factStateStreamCheck($postfixSourceLedger, '胰岛素未记录。', false, 'source-side clinical deficiency is not a postfix record gap', 'POSITIVE_TO_UNKNOWN', '未记录'); $v33Wire = str_replace('。', "。\r\n", $postfixRecordParagraph) . "\r\n"; $v33Visible = ''; $v33Stream = new TcmClinicalReportStream($pilotLedger, static function (string $delta) use (&$v33Visible): bool { $v33Visible .= $delta; return true; }); $v33Pushed = true; foreach (str_split($v33Wire, 7) as $chunk) { if (!$v33Stream->push($chunk)) { $v33Pushed = false; break; } } factStateExpect($v33Pushed && $v33Stream->finish() && !$v33Stream->rejected(), 'the portable v33 paragraph survives split UTF-8, source annotations and CRLF boundaries'); factStateExpect($v33Visible === $v33Wire, 'v33 role analysis preserves every accepted original byte'); // The exclusion predicate is inside a modal limit, not a patient denial. $exclusionLimitParagraph = '表里:症状持续3个月、渐进出现,以乏力、动则气短、自汗、口干及二便改变为主,现有资料更支持里证倾向。' . '限制依据:见第一节阴性第①、⑥项(s2),只能降低本次急性表证可能,不能仅凭否认彻底排除所有表证。'; factStateStreamCheck($pilotLedger, $exclusionLimitParagraph, true, 'the complete frozen B OpenAI paragraph limits exclusion rather than denying a patient finding'); $exclusionLedger = TcmClinicalFactStateGuard::build([ 'present_illness' => '口干;无心悸;未记录表证、耳鸣或固定刺痛。', 'tongue' => '舌质淡红。', 'pulse' => '脉细弱。', ]); $exclusionLimit = '不能仅凭否认彻底排除所有表证'; foreach ([$exclusionLimit . '。', '本次依据有限,' . $exclusionLimit . '。', '不能排除耳鸣。', '不能彻底排除表证。', '不能排除固定刺痛或耳鸣。', '不得单凭现有资料完全排除耳鸣。', '不应仅依据上述证据排除表证。', '不得单凭本次资料完全排除耳鸣。', '不能仅凭当前阴性记录排除表证。', '不能仅据阴性记录直接排除所有表证。', '不能只凭否认排除全部表证。', '不据此宣称表证已被排除。', '但不据此宣称表证已被排除。', '不代表表证已被排除。', '不能据此宣称表证已被排除。', '不说明耳鸣已经被完全排除。', $exclusionLimit . ',口干,脉细弱。'] as $modalLimit) { factStateStreamCheck($exclusionLedger, $modalLimit, true, 'a closed modal exclusion limit contains a neutral source noun and neutral target object'); } foreach ([$exclusionLimit . ',患者有耳鸣。', '患者有耳鸣,' . $exclusionLimit . '。', $exclusionLimit . '且患者有耳鸣。', $exclusionLimit . '患者有耳鸣。', '不能排除患者有耳鸣。'] as $mixedLimit) { factStateStreamCheck($exclusionLedger, $mixedLimit, false, 'a modal exclusion limit cannot absorb an actual patient positive', 'UNKNOWN_TO_POSITIVE', '有耳鸣'); } foreach ([$exclusionLimit . ',患者无固定刺痛。', '患者无固定刺痛,' . $exclusionLimit . '。', $exclusionLimit . '且患者无固定刺痛。', $exclusionLimit . '患者无固定刺痛。', '不能排除患者无固定刺痛。', '不能仅凭患者无固定刺痛彻底排除表证。', '不能仅凭否认固定刺痛彻底排除表证。'] as $mixedLimit) { factStateStreamCheck($exclusionLedger, $mixedLimit, false, 'a modal limit cannot turn an embedded or adjacent clinical denial into a neutral noun', 'UNKNOWN_TO_NEGATIVE', '固定刺痛'); } foreach ([$exclusionLimit . ',患者有心悸。', $exclusionLimit . '且患者有心悸。'] as $mixedLimit) { factStateStreamCheck($exclusionLedger, $mixedLimit, false, 'a modal exclusion limit cannot reverse a known negative', 'NEGATIVE_TO_POSITIVE', '有心悸'); } foreach ([$exclusionLimit . ',口干未记录。', $exclusionLimit . '且口干未记录。'] as $mixedLimit) { factStateStreamCheck($exclusionLedger, $mixedLimit, false, 'a modal exclusion limit cannot erase a known positive', 'POSITIVE_TO_UNKNOWN', '未记录'); } factStateStreamCheck($exclusionLedger, $exclusionLimit . ',心悸未记录。', false, 'a modal exclusion limit cannot erase a known negative', 'NEGATIVE_TO_UNKNOWN', '未记录'); foreach (['本次彻底排除表证。', '本次已排除表证。', '表证已被排除。', '据此宣称表证已被排除。'] as $actualExclusion) { factStateStreamCheck($exclusionLedger, $actualExclusion, false, 'an actual exclusion without modal scope receives no exemption', 'UNKNOWN_TO_POSITIVE', '表证'); } $exclusionScopeMethod = new ReflectionMethod(TcmClinicalFactStateGuard::class, 'exclusionLimitProse'); $exclusionScopeMethod->setAccessible(true); foreach (['排除表证', '本次彻底排除表证', '仅凭阴性记录彻底排除表证', '患者无固定刺痛', '表证已被排除', '据此宣称表证已被排除', '不能仅凭否认固定刺痛彻底排除表证', '舌质淡红而非明确红绛'] as $actualPredicate) { factStateExpect($exclusionScopeMethod->invoke(null, $actualPredicate) === $actualPredicate, 'exclusion normalization preserves every byte of an actual or unbounded predicate'); } factStateStreamCheck($exclusionLedger, '本次否认耳鸣。', false, 'an actual patient denial without modal scope remains a clinical negative', 'UNKNOWN_TO_NEGATIVE', '否认耳鸣'); factStateStreamCheck($pilotLedger, $exclusionLimit . ',无五心烦热。', false, 'a modal limit cannot authorize degree-qualifier loss', 'NEGATION_SCOPE_WIDENED', '无五心烦热'); factStateStreamCheck($pilotLedger, $exclusionLimit . '且脉滑。', false, 'a joined actual pulse value cannot borrow modal exclusion scope', 'PULSE_FEATURE_NOT_IN_CURRENT_SOURCE', '脉滑'); factStateStreamCheck($pilotLedger, $exclusionLimit . ',舌质红绛。', false, 'an actual tongue value cannot borrow modal exclusion scope', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '红绛'); factStateStreamCheck($pilotLedger, $exclusionLimit . ',舌质淡红而非明确红绛。', false, 'the frozen B tongue-color negation remains unsupported after modal scoping', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '非明确红绛'); factStateStreamCheck($pilotLedger, $exclusionLimit . '且舌质淡红而非明确红绛。', false, 'a joined tongue-color negation remains unsupported after modal scoping', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '非明确红绛'); foreach (['不能排除脉滑。', $exclusionLimit . '脉滑。'] as $phenotypeLimit) { factStateStreamCheck($pilotLedger, $phenotypeLimit, false, 'modal normalization leaves pulse phenotypes under the existing examination policy', 'PULSE_FEATURE_NOT_IN_CURRENT_SOURCE', '脉滑'); } foreach (['不能排除舌质红绛。', $exclusionLimit . '舌质红绛。'] as $phenotypeLimit) { factStateStreamCheck($pilotLedger, $phenotypeLimit, false, 'modal normalization leaves tongue phenotypes under the existing examination policy', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '红绛'); } factStateStreamCheck($pilotLedger, $exclusionLimit . '(s999)。', false, 'modal scope cannot validate an invalid source annotation', 'SOURCE_ANNOTATION_INVALID', '排除'); foreach (['不据此宣称相关证候已被排除', '不代表相关证候已被排除'] as $passiveLimit) { factStateStreamCheck($pilotLedger, $passiveLimit . '。', true, 'a denial of an exclusion conclusion stays inside its modal scope'); factStateStreamCheck($pilotLedger, $passiveLimit . '且患者无固定刺痛。', false, 'a passive exclusion limit cannot absorb a joined patient absence', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($pilotLedger, $passiveLimit . ',患者有心悸。', false, 'a passive exclusion limit cannot reverse a known patient negative', 'NEGATIVE_TO_POSITIVE', '有心悸'); factStateStreamCheck($pilotLedger, $passiveLimit . '且口干未记录。', false, 'a passive exclusion limit cannot erase a known finding', 'POSITIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($pilotLedger, $passiveLimit . '且舌质淡红而非明确红绛。', false, 'a passive exclusion limit cannot reinterpret a tongue phenotype', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '非明确红绛'); } $attachmentRoleParagraph = '反证与限制:限制依据见第一节已记录阴性第2、3、4项(s2);这些资料降低显著实热、虚热及阳虚寒象对本方向的支持,但不据此宣称相关证候已被排除。' . '限制依据另见第一节已记录阴性第5项(s2)和第7项(s3);仅说明当前缺乏相应心肺急症伴随表现,不能替代心肺检查。' . '舌脉虽有本次医师记录,但无附件可供二次图像核验,仍应由复核医师结合现场所见确认。'; $denialReferenceParagraph = '反证与局限:限制依据见第一节已记录阴性第2、3、4项(s2),这仅提示典型热盛、虚热或阳虚寒象的支持不足,不代表相关证候已被排除;' . '见第5、7项(s2、s3),仅降低明显心肺伴随症状对当前判断的干扰,不能代替心肺评估。既往疾病否认见第8项(s4),亦不能替代本次检查。'; foreach ([$attachmentRoleParagraph, $denialReferenceParagraph] as $metadataParagraph) { factStateStreamCheck($pilotLedger, $metadataParagraph, true, 'a complete retained A paragraph preserves bounded availability or source-verified history reference roles'); } $attachmentRole = '无附件可供二次图像核验'; foreach ([$attachmentRole . '。', '未提供舌照可供二次复核。', '无图像可供图片核验。', '舌脉有本次医师记录,但' . $attachmentRole . ',仍应复核。'] as $availability) { factStateStreamCheck($pilotLedger, $availability, true, 'a closed attachment availability object is metadata rather than a clinical absence'); } foreach ([$attachmentRole . ',患者无固定刺痛。', '患者无固定刺痛,' . $attachmentRole . '。', $attachmentRole . '且患者无固定刺痛。', $attachmentRole . '患者无固定刺痛。', $attachmentRole . '、固定刺痛。'] as $mixedAvailability) { factStateStreamCheck($pilotLedger, $mixedAvailability, false, 'attachment availability cannot absorb actual clinical absences or list items', 'UNKNOWN_TO_NEGATIVE', '固定刺痛'); } factStateStreamCheck($pilotLedger, $attachmentRole . '且患者有心悸。', false, 'attachment availability cannot reverse a known negative', 'NEGATIVE_TO_POSITIVE', '有心悸'); factStateStreamCheck($pilotLedger, $attachmentRole . '且口干未记录。', false, 'attachment availability cannot erase a known positive', 'POSITIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($pilotLedger, $attachmentRole . '且脉滑。', false, 'attachment availability cannot authorize a new pulse phenotype', 'PULSE_FEATURE_NOT_IN_CURRENT_SOURCE', '脉滑'); factStateStreamCheck($pilotLedger, '本次' . $attachmentRole . '及皮疹。', false, 'a clinical list cannot be reclassified as one attachment object', 'NEGATIVE_NOT_IN_SOURCE', '皮疹'); factStateStreamCheck($pilotLedger, $attachmentRole . '(s999)。', false, 'attachment metadata cannot validate a nonexistent citation', 'SOURCE_ANNOTATION_INVALID', '核验'); $referenceLedger = TcmClinicalFactStateGuard::build([ 'id' => 1, 'diagnosis_date' => '2026-10-23', 'present_illness' => '口干;无心悸;未记录固定刺痛或耳鸣。', 'past_history' => '无已知冠心病。', 'allergy_history' => '未记录', 'family_history' => '否认', ]); $referenceIds = []; foreach ($referenceLedger['sources'] as $source) $referenceIds[$source['field']] = $source['id']; $denialReference = '既往疾病否认见第8项(' . $referenceIds['past_history'] . ')'; foreach ([$denialReference . '。', $denialReference . ',亦不能替代本次检查。', '既往史否认见第8项(2026-10-23,' . $referenceIds['past_history'] . ')。', '家族史否认见第⑧项(' . $referenceIds['family_history'] . ')。'] as $verifiedReference) { factStateStreamCheck($referenceLedger, $verifiedReference, true, 'a neutral history locator cites a current negative source in the same history field'); } foreach (['既往疾病否认见第8项。', '既往疾病否认见第8项(s999)。', '既往疾病否认见第8项(' . $referenceIds['present_illness'] . ')。', '过敏史否认见第8项(' . $referenceIds['allergy_history'] . ')。', '既往疾病否认见第8项(2026-10-22,' . $referenceIds['past_history'] . ')。'] as $invalidReference) { factStateStreamCheck($referenceLedger, $invalidReference, false, 'a history locator needs a real same-time negative source in its own history field', 'SOURCE_ANNOTATION_INVALID', '否认见'); } factStateStreamCheck($referenceLedger, $denialReference . '且患者无固定刺痛。', false, 'a verified history locator cannot absorb a patient absence', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($referenceLedger, $denialReference . ',患者有心悸。', false, 'a verified history locator cannot reverse another negative', 'NEGATIVE_TO_POSITIVE', '有心悸'); factStateStreamCheck($referenceLedger, $denialReference . '且口干未记录。', false, 'a verified history locator cannot erase a known finding', 'POSITIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($referenceLedger, '固定刺痛否认见第8项(' . $referenceIds['past_history'] . ')。', false, 'a named clinical symptom is not a neutral history-field locator', 'UNKNOWN_TO_POSITIVE', '固定刺痛'); factStateStreamCheck($referenceLedger, '既往疾病否认见第8项(' . $referenceIds['past_history'] . ',当前患者无固定刺痛)。', false, 'a mixed clinical citation cannot borrow history-reference scope', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); factStateStreamCheck($pilotLedger, '既往疾病否认见第8项(s4),舌质淡红而非明确红绛。', false, 'a valid history reference cannot authorize an unsupported tongue comparison', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '非明确红绛'); $historicalReferenceLedger = TcmClinicalFactStateGuard::build([ 'id' => 1, 'diagnosis_date' => '2026-10-23', 'past_history' => '未记录', ], ['required_content' => [['key' => 'past_history', 'value' => [[ 'id' => 2, 'diagnosis_date' => '2026-10-22', 'past_history' => '否认', ]]]]]); $historicalReferenceSource = array_values(array_filter($historicalReferenceLedger['sources'], static fn (array $source): bool => $source['scope'] === 'historical'))[0]; factStateStreamCheck($historicalReferenceLedger, '既往疾病否认见第8项(' . $historicalReferenceSource['id'] . ')。', false, 'a historical denial cannot supply a current history reference', 'SOURCE_ANNOTATION_INVALID', '否认见'); foreach (['糖尿病4年。', '既往否认冠心病。', '无附件。', '否认记录有误。'] as $unsupportedHistory) { $unsupportedReferenceLedger = TcmClinicalFactStateGuard::build([ 'diagnosis_date' => '2026-10-23', 'past_history' => $unsupportedHistory, ]); factStateStreamCheck($unsupportedReferenceLedger, '既往疾病否认见第8项(s1)。', false, 'a positive, historical or metadata-only history source cannot authorize a current denial locator', 'SOURCE_ANNOTATION_INVALID', '否认见'); } $v33ScopeWire = implode("\r\n\r\n", [$postfixRecordParagraph, $exclusionLimitParagraph, $attachmentRoleParagraph, $denialReferenceParagraph]) . "\r\n\r\n"; $v33ScopeVisible = ''; $v33ScopeStream = new TcmClinicalReportStream($pilotLedger, static function (string $delta) use (&$v33ScopeVisible): bool { $v33ScopeVisible .= $delta; return true; }); $v33ScopePushed = true; foreach (str_split($v33ScopeWire, 7) as $chunk) { if (!$v33ScopeStream->push($chunk)) { $v33ScopePushed = false; break; } } factStateExpect($v33ScopePushed && $v33ScopeStream->finish() && !$v33ScopeStream->rejected(), 'all v33 scoped roles survive split UTF-8, source annotations and CRLF paragraph boundaries together'); factStateExpect($v33ScopeVisible === $v33ScopeWire, 'all accepted v33 scoped roles preserve every original streamed byte'); // Follow-up: verification is a bounded review action, not an observed noun list. $verifySafetyParagraph = '禁忌与安全:虽本次询问无已知药物过敏史,首次用药仍须核对人参、麦冬、五味子及制剂辅料的既往反应。' . '对药材质量、胃肠耐受、肝肾功能变化及潜在相互作用需复核;妊娠事项不适用于本例男性。' . '2026-10-20 ALT 22U/L、肌酐78μmol/L、eGFR 96mL/min/1.73m²、尿ACR 12mg/g,可作当前安全基线,但不能保证后续用药绝对安全。'; factStateStreamCheck($pilotLedger, $verifySafetyParagraph, true, 'the complete retained v33 safety paragraph preserves its verification-action role'); $verifyAction = '首次用药仍须核对药材及制剂辅料的既往反应'; foreach ([$verifyAction . '。', '用药前需逐项核对既往反应及合并药物。', '复诊仍应核对过敏、肝肾功能。', '仍须核对既往不良反应。', '虽本次询问无已知药物过敏史,' . $verifyAction . '。'] as $neutralVerification) { factStateStreamCheck($pilotLedger, $neutralVerification, true, 'a modal verification action governs only its neutral object list'); } foreach (['患者无固定刺痛,' . $verifyAction . '。', $verifyAction . ',患者无固定刺痛。', $verifyAction . '且患者无固定刺痛。', $verifyAction . '患者无固定刺痛。', '首次用药须核对无固定刺痛。', '须核对患者无固定刺痛。'] as $mixedVerification) { factStateStreamCheck($pilotLedger, $mixedVerification, false, 'verification cannot absorb an actual negative prefix, suffix or object assertion', 'UNKNOWN_TO_NEGATIVE', '无固定刺痛'); } foreach (['患者有心悸,' . $verifyAction . '。', $verifyAction . ',患者有心悸。', $verifyAction . '且患者有心悸。', $verifyAction . '患者有心悸。', '首次用药须核对患者有心悸。', '须核对患者有心悸。', '需核对患者有心悸。', '首次用药仍须核对有心悸。'] as $mixedVerification) { factStateStreamCheck($pilotLedger, $mixedVerification, false, 'verification cannot absorb an actual positive and cannot borrow a generic modal exemption', 'NEGATIVE_TO_POSITIVE', '有心悸'); } factStateStreamCheck($pilotLedger, $verifyAction . '且口干未记录。', false, 'a verification action cannot erase a known positive', 'POSITIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($pilotLedger, $verifyAction . ',心悸未记录。', false, 'a verification action cannot erase a known negative', 'NEGATIVE_TO_UNKNOWN', '未记录'); factStateStreamCheck($gapLedger, $verifyAction . '且患者有耳鸣。', false, 'a historical noun in a verification object cannot redate a new current assertion', 'UNKNOWN_TO_POSITIVE', '有耳鸣'); factStateStreamCheck($pilotLedger, $verifyAction . '且舌质红绛。', false, 'a verification action cannot authorize a new tongue phenotype', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '红绛'); factStateStreamCheck($pilotLedger, $verifyAction . '且脉滑。', false, 'a verification action cannot authorize a new pulse phenotype', 'PULSE_FEATURE_NOT_IN_CURRENT_SOURCE', '脉滑'); factStateStreamCheck($pilotLedger, $verifyAction . ',舌质淡红而非明确红绛。', false, 'a verification action cannot reinterpret the unsupported tongue comparison', 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE', '非明确红绛'); factStateStreamCheck($pilotLedger, '虽本次询问无药物过敏史,' . $verifyAction . '。', false, 'a verification action cannot authorize deleting the known-allergy qualifier', 'NEGATION_SCOPE_WIDENED', '无药物过敏史'); factStateStreamCheck($allergyUnknown, '虽本次询问无已知药物过敏史,' . $verifyAction . '。', false, 'a verification action cannot make an unknown allergy history an observed negative', 'UNKNOWN_TO_NEGATIVE', '无已知药物过敏史'); factStateStreamCheck($pilotLedger, $verifyAction . '(s999)。', false, 'verification cannot validate a nonexistent source citation', 'SOURCE_ANNOTATION_INVALID', '核对'); $verifyWire = $verifySafetyParagraph . "\r\n\r\n"; $verifyVisible = ''; $verifyStream = new TcmClinicalReportStream($pilotLedger, static function (string $delta) use (&$verifyVisible): bool { $verifyVisible .= $delta; return true; }); $verifyPushed = true; foreach (str_split($verifyWire, 7) as $chunk) { if (!$verifyStream->push($chunk)) { $verifyPushed = false; break; } } factStateExpect($verifyPushed && $verifyStream->finish() && !$verifyStream->rejected(), 'the complete safety verification paragraph survives split UTF-8 and CRLF boundaries'); factStateExpect($verifyVisible === $verifyWire, 'safety verification preserves every accepted original byte'); $retainedParagraphs = [ 'tcm_complete_v33_original_single_20261024_1_openai.rejected.json' => [true, ''], 'tcm_complete_v32_workflow_a_c4_20261024_2_openai.rejected.json' => [true, ''], 'tcm_complete_v32_workflow_a_c4_20261024_3_openai.rejected.json' => [true, ''], 'tcm_complete_v32_workflow_b_c4_20261024_3_openai.rejected.json' => [true, ''], 'tcm_complete_v32_workflow_b_c4_20261024_2_openai.rejected.json' => [false, 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE'], 'tcm_complete_v32_workflow_a_single_20261024_1_openai.rejected.json' => [true, ''], 'tcm_complete_v32_workflow_a_single_20261024_0_qwen.rejected.json' => [false, 'NEGATION_SCOPE_WIDENED'], 'tcm_complete_v31_isolated_single_20261023_1_openai.rejected.json' => [true, ''], 'tcm_complete_v31_isolated_c4_20261023_2_openai.rejected.json' => [true, ''], 'tcm_complete_v31_isolated_c4_20261023_3_openai.rejected.json' => [false, 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE'], 'tcm_complete_v31_isolated_single_20261023_0_qwen.rejected.json' => [false, 'NEGATION_SCOPE_WIDENED'], 'tcm_complete_v31_isolated_c4_20261023_0_qwen.rejected.json' => [false, 'NEGATION_SCOPE_WIDENED'], 'tcm_complete_v31_isolated_c4_20261023_1_qwen.rejected.json' => [false, 'NEGATION_SCOPE_WIDENED'], 'tcm_complete_v29_isolated_deadline_after_20261023_openai_next_full90_openai.rejected.json' => [true, ''], 'tcm_complete_v30_isolated_single_20261023_1_openai.rejected.json' => [true, ''], 'tcm_complete_v30_isolated_c4_20261023_2_openai.rejected.json' => [true, ''], 'tcm_complete_v30_isolated_c4_20261023_3_openai.rejected.json' => [true, ''], 'tcm_complete_v30_isolated_single_20261023_0_qwen.rejected.json' => [false, 'UNKNOWN_TO_NEGATIVE'], 'tcm_complete_v30_isolated_c4_20261023_0_qwen.rejected.json' => [false, 'NEGATION_SCOPE_WIDENED'], 'tcm_complete_v30_isolated_c4_20261023_1_qwen.rejected.json' => [false, 'NEGATION_SCOPE_WIDENED'], 'tcm_complete_v29_isolated_single_20261023_1_openai.rejected.json' => [true, ''], 'tcm_complete_v29_isolated_c4_20261023_3_openai.rejected.json' => [true, ''], 'tcm_complete_v29_isolated_single_20261023_0_qwen.rejected.json' => [false, 'NEGATION_SCOPE_WIDENED'], 'tcm_complete_v29_isolated_c4_20261023_0_qwen.rejected.json' => [false, 'NEGATION_SCOPE_WIDENED'], 'tcm_complete_v29_isolated_c4_20261023_1_qwen.rejected.json' => [false, 'NEGATION_SCOPE_WIDENED'], 'tcm_complete_v28_isolated_single_20261023_1_openai.rejected.json' => [true, ''], 'tcm_complete_v28_isolated_c4_20261023_2_openai.rejected.json' => [true, ''], 'tcm_complete_v28_isolated_c4_20261023_3_openai.rejected.json' => [true, ''], 'tcm_complete_v28_isolated_single_20261023_0_qwen.rejected.json' => [false, 'NEGATION_SCOPE_WIDENED'], 'tcm_complete_v28_isolated_c4_20261023_0_qwen.rejected.json' => [false, 'NEGATION_SCOPE_WIDENED'], 'tcm_complete_v28_isolated_c4_20261023_1_qwen.rejected.json' => [false, 'NEGATION_SCOPE_WIDENED'], 'tcm_complete_v26_single_fixed_1_openai.rejected.json' => [true, ''], 'tcm_complete_v26_c4_fixed_2_openai.rejected.json' => [true, ''], 'tcm_complete_v26_c4_fixed_3_openai.rejected.json' => [true, ''], 'tcm_complete_v26_single_fixed_0_qwen.rejected.json' => [false, 'NEGATION_SCOPE_WIDENED'], 'tcm_complete_v26_c4_fixed_0_qwen.rejected.json' => [false, 'NEGATION_SCOPE_WIDENED'], 'tcm_complete_v26_c4_fixed_1_qwen.rejected.json' => [false, 'NEGATION_SCOPE_WIDENED'], 'tcm_complete_v25_single_fixed_1_openai.rejected.json' => [true, ''], 'tcm_complete_v25_c4_fixed_2_openai.rejected.json' => [true, ''], 'tcm_complete_v25_c4_fixed_3_openai.rejected.json' => [true, ''], 'tcm_complete_v25_single_fixed_0_qwen.rejected.json' => [false, 'NEGATION_SCOPE_WIDENED'], 'tcm_complete_v25_c4_fixed_0_qwen.rejected.json' => [false, 'NEGATION_SCOPE_WIDENED'], 'tcm_complete_v25_c4_fixed_1_qwen.rejected.json' => [false, 'NEGATION_SCOPE_WIDENED'], 'tcm_complete_v24_single_fixed_0_qwen.rejected.json' => [true, ''], 'tcm_complete_v24_single_fixed_1_openai.rejected.json' => [true, ''], 'tcm_complete_v24_c4_fixed_0_qwen.rejected.json' => [true, ''], 'tcm_complete_v24_c4_fixed_1_qwen.rejected.json' => [true, ''], 'tcm_complete_v24_c4_fixed_2_openai.rejected.json' => [true, ''], 'tcm_complete_v24_c4_fixed_3_openai.rejected.json' => [true, ''], 'tcm_complete_v23_c4_fixed_3_openai.rejected.json' => [true, ''], 'tcm_complete_v23_c4_fixed_2_openai.rejected.json' => [true, ''], 'tcm_complete_v23_single_fixed_0_qwen.rejected.json' => [false, 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE'], 'tcm_complete_v23_c4_fixed_1_qwen.rejected.json' => [false, 'NEGATION_SCOPE_WIDENED'], 'tcm_complete_v23_single_fixed_1_openai.rejected.json' => [false, 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE'], 'tcm_complete_v22_qwen_single_fixed_0_qwen.rejected.json' => [true, ''], 'tcm_complete_v22_openai_single_fixed_0_openai.rejected.json' => [true, ''], 'tcm_complete_v21_mixed2_fixed_1_openai.rejected.json' => [false, 'NEGATIVE_TO_UNKNOWN'], 'tcm_complete_v20_mixed2_fixed_1_openai.rejected.json' => [true, ''], 'tcm_complete_v19_c4_fixed_2_openai.rejected.json' => [true, ''], 'tcm_complete_v18_c4_fixed_2_openai.rejected.json' => [true, ''], 'tcm_complete_v18_c4_fixed_3_openai.rejected.json' => [true, ''], 'tcm_complete_v14_staged_pilot3_1_openai.rejected.json' => [true, ''], 'tcm_complete_v14_staged_pilot2_1_openai.rejected.json' => [true, ''], 'tcm_complete_v14_staged_pilot4_1_openai.rejected.json' => [true, ''], 'tcm_complete_v14_single_fixed_1_openai.rejected.json' => [true, ''], 'tcm_complete_v15_single_fixed_1_openai.rejected.json' => [true, ''], 'tcm_complete_v17_single_fixed_1_openai.rejected.json' => [true, ''], 'tcm_complete_v17_c4_fixed_2_openai.rejected.json' => [false, 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE'], 'tcm_complete_v17_c4_fixed_3_openai.rejected.json' => [false, 'TONGUE_FEATURE_NOT_IN_CURRENT_SOURCE'], 'tcm_complete_v17_c4_fixed_0_qwen.rejected.json' => [false, 'NEGATION_SCOPE_WIDENED'], 'tcm_complete_v17_c4_fixed_1_qwen.rejected.json' => [false, 'NEGATION_SCOPE_WIDENED'], 'tcm_complete_v16_single_fixed_1_openai.rejected.json' => [true, ''], 'tcm_complete_v16_c4_fixed_2_openai.rejected.json' => [true, ''], 'tcm_complete_v16_c4_fixed_3_openai.rejected.json' => [true, ''], 'tcm_complete_v14_staged_pilot3_0_qwen.rejected.json' => [false, 'NEGATIVE_NOT_IN_SOURCE'], 'tcm_complete_v14_staged_pilot2_0_qwen.rejected.json' => [false, 'NEGATION_SCOPE_WIDENED'], ]; if (!$replayLocalArtifacts) $retainedParagraphs = []; foreach ($retainedParagraphs as $file => [$valid, $code]) { $path = $artifactRoot . $file; if (!is_file($path)) continue; $saved = json_decode((string) file_get_contents($path), true); factStateExpect(is_array($saved['rejections'] ?? null) && $saved['rejections'] !== [], 'pilot artifact contains the complete rejected paragraph'); foreach ($saved['rejections'] ?? [] as $rejection) { factStateCheck($pilotLedger, (string) $rejection['unit'], $valid, 'full retained pilot paragraph: ' . $file, $code); } } factStateCheck([], '无固定刺痛。', false, 'missing ledger cannot produce a successful validation', 'FACT_STATE_LEDGER_UNAVAILABLE'); factStateCheck($ledger, "\xC3\x28", false, 'malformed UTF-8 is not silently accepted', 'FACT_STATE_INVALID_UTF8'); factStateExpect(str_contains(TcmClinicalFactStateGuard::prompt($fixtureLedger), '固定刺痛') && mb_strlen(TcmClinicalFactStateGuard::prompt($fixtureLedger), 'UTF-8') < 700, 'compact prompt points to explicit unknown topics without duplicating the case'); if ($failures !== []) { fwrite(STDERR, 'Clinical fact state: ' . count($failures) . ' failures / ' . $assertions . " assertions\n- " . implode("\n- ", $failures) . "\n"); exit(1); } echo 'Clinical fact state: ' . $assertions . " assertions passed.\n";