90 lines
8.0 KiB
PHP
90 lines
8.0 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace app\adminapi\service {
|
|
// Isolate delivery mechanics from peers being changed independently.
|
|
final class TcmClinicalFactStateGuard {
|
|
public static function validate(array $ledger, string $text): array {
|
|
return ['valid' => !str_contains($text, '[FACT_REJECT]'), 'issues' => [['code' => 'FACT_TEST_FAILURE', 'source_id' => 's1']]];
|
|
}
|
|
}
|
|
final class TcmClinicalEvidenceContract {
|
|
public static function validate(array $index, string $text, bool $complete = false): array {
|
|
return ['valid' => !str_contains($text, '[SOURCE_REJECT]'), 'issues' => [['code' => 'SOURCE_TEST_FAILURE', 'source_id' => 's1']]];
|
|
}
|
|
}
|
|
}
|
|
namespace {
|
|
require dirname(__DIR__) . '/app/adminapi/service/TcmLongReportContract.php';
|
|
require dirname(__DIR__) . '/app/adminapi/service/TcmHerbalPremiseGuard.php';
|
|
require dirname(__DIR__) . '/app/adminapi/service/TcmClinicalReportStream.php';
|
|
use app\adminapi\service\TcmClinicalReportStream as Stream;
|
|
use app\adminapi\service\TcmLongReportContract as Shape;
|
|
$checks = 0;
|
|
function checkpointExpect(bool $ok, string $message): void { ++$GLOBALS['checks']; if (!$ok) throw new RuntimeException($message); }
|
|
$query = '<USER_QUESTION>生成1800至2200字完整中医辨证长报告。</USER_QUESTION>';
|
|
$engineering = '本段用于离线交付机制验证,文字不代表患者事实、诊断或处方,真实临床证据需独立审核。';
|
|
$first = "一、四诊\n\n望诊:未记录。\n\n闻诊:未记录。\n\n问诊:工程资料单列,本段只演示来源整理,所有临床结论均需独立核对,不以传输测试文字代替真实患者信息。\n\n切诊:其他触诊未记录,脉象见独立脉象项。\n\n舌象:工程来源仅演示独立记录。\n\n脉象:工程来源仅演示独立记录。\n\n";
|
|
$prefix = $first;
|
|
foreach (['二、八纲', '三、气血津液与脏腑', '四、证名', '五、治法'] as $heading) $prefix .= $heading . "\n\n" . str_repeat($engineering, 3) . "\n\n";
|
|
function checkpointTail(string $prefix, int $characters, string $intro = '主方方向:暂不列方,继续补采及监测。'): string {
|
|
$tail = "六、中药方药及复核监测\n\n" . $intro . "\n\n" . str_repeat($GLOBALS['engineering'], 3);
|
|
$tail .= str_repeat('核', max(0, $characters - mb_strlen($prefix . $tail, 'UTF-8')));
|
|
return $tail;
|
|
}
|
|
$tail = checkpointTail($prefix, 2000);
|
|
$whole = $prefix . $tail;
|
|
checkpointExpect(Shape::validate($whole, $query)['valid'], 'Engineering fixture satisfies the unchanged full range and section-body contract.');
|
|
$visible = ''; $rejected = [];
|
|
$stream = new Stream([], static function (string $s) use (&$visible): bool { $visible .= $s; return true; },
|
|
static function ($s, $issues) use (&$rejected): void { $rejected[] = [$s, $issues]; }, '', null, [], $query);
|
|
foreach (str_split($whole, 1) as $byte) checkpointExpect($stream->push($byte), 'One-byte UTF-8/header boundaries remain buffered correctly.');
|
|
checkpointExpect($visible === $prefix && $stream->acceptedContent() === $prefix, 'No byte of heading six or its tail is visible before completion.');
|
|
checkpointExpect($stream->finish() && $visible === $whole && $stream->acceptedContent() === $whole, 'Valid original model bytes flush unchanged after whole-report validation.');
|
|
checkpointExpect($rejected === [], 'Valid tail does not invoke repair feedback.');
|
|
|
|
$visible = ''; $bad = checkpointTail($prefix, 2000, '主方方向:甲乙散。关键方证证据不足,限医师复核。');
|
|
$stream = new Stream([], static function ($s) use (&$visible): bool { $visible .= $s; return true; },
|
|
static function ($s, $issues) use (&$rejected): void { $rejected = [$s, $issues]; }, '', null, [], $query);
|
|
checkpointExpect($stream->push($prefix), 'First five sections continue streaming.');
|
|
checkpointExpect(!$stream->push($bad . "\n\n") && $visible === $prefix, 'Complete contradiction paragraph rejects before any section-six byte is delivered.');
|
|
checkpointExpect(str_starts_with($rejected[0], '六、') && in_array('REPORT_FINAL_SECTION_UNDELIVERED', array_column($rejected[1], 'code'), true), 'Repair feedback includes the whole undelivered sixth-section draft.');
|
|
|
|
foreach ([2221, 1750] as $length) {
|
|
$visible = ''; $draft = checkpointTail($prefix, $length);
|
|
$stream = new Stream([], static function ($s) use (&$visible): bool { $visible .= $s; return true; }, null, '', null, [], $query);
|
|
checkpointExpect($stream->push($prefix . $draft), 'A shape-invalid final draft is privately buffered until completion.');
|
|
checkpointExpect(!$stream->finish() && $visible === $prefix && !$stream->finalContract()['valid'], 'Original lower/upper length failures never expose the buffered tail.');
|
|
}
|
|
$earlyPrefix = substr($prefix, 0, strpos($prefix, '三、'));
|
|
$visible = ''; $rejected = [];
|
|
$stream = new Stream([], static function ($s) use (&$visible): bool { $visible .= $s; return true; },
|
|
static function ($s, $issues) use (&$rejected): void { $rejected = [$s, $issues]; }, '', null, [], $query);
|
|
checkpointExpect($stream->push($earlyPrefix) && !$stream->finish() && $visible === $earlyPrefix,
|
|
'A naturally truncated first-two-section prefix is retained exactly and fails unchanged whole shape.');
|
|
checkpointExpect($rejected[0] === '' && in_array('REPORT_FINAL_CONTRACT_INVALID', array_column($rejected[1], 'code'), true)
|
|
&& !in_array('REPORT_FINAL_SECTION_UNDELIVERED', array_column($rejected[1], 'code'), true),
|
|
'No sixth-section restart marker is fabricated before a sixth heading was actually held.');
|
|
foreach (['[FACT_REJECT]', '[SOURCE_REJECT]'] as $marker) {
|
|
$visible = '';
|
|
$stream = new Stream([], static function ($s) use (&$visible): bool { $visible .= $s; return true; }, null, '', null, [], $query);
|
|
checkpointExpect(!$stream->push($prefix . "六、方药\n\n" . $marker . "\n\n") && $visible === $prefix, 'Existing fact/provenance rejection still protects the entire undelivered tail.');
|
|
}
|
|
$visible = ''; $abort = false;
|
|
$stream = new Stream([], static function ($s) use (&$visible): bool { $visible .= $s; return true; }, null, '', null, [], $query, static fn (): bool => $GLOBALS['abort']);
|
|
checkpointExpect($stream->push($whole), 'A valid tail can wait without client delivery.');
|
|
$abort = true;
|
|
checkpointExpect(!$stream->finish() && $visible === $prefix && !$stream->rejected(), 'Disconnect/cancellation before flush cannot release the tail or become a clinical repair.');
|
|
$visible = '';
|
|
$stream = new Stream([], static function ($s) use (&$visible): bool { if (str_starts_with($s, '六、')) return false; $visible .= $s; return true; }, null, '', null, [], $query);
|
|
checkpointExpect($stream->push($whole) && !$stream->finish() && $visible === $prefix && $stream->acceptedContent() === $prefix && !$stream->rejected(), 'Callback refusal during atomic tail flush retains exact accepted-prefix semantics.');
|
|
$visible = '';
|
|
$stream = new Stream([], static function ($s) use (&$visible): bool { $visible .= $s; return true; }, null, '', null, [], $query);
|
|
checkpointExpect($stream->push($prefix . "六、方药\n\n") && !$stream->push(str_repeat('x', 100001)), 'Deferred bytes count toward the original100k bound.');
|
|
checkpointExpect($visible === $prefix && $stream->issues()[0]['code'] === 'REPORT_BOUND_EXCEEDED', 'Bound overflow never flushes private tail bytes.');
|
|
$visible = '';
|
|
$ordinary = new Stream([], static function ($s) use (&$visible): bool { $visible .= $s; return true; });
|
|
checkpointExpect($ordinary->push("六、方药\n\n主方方向:甲乙散。关键方证证据不足。\n\n") && str_contains($visible, '甲乙散'), 'Non-opted ordinary streams retain the prior paragraph delivery behavior.');
|
|
echo "TCM final-section checkpoint: {$checks} assertions passed (isolated transport mechanics; no clinical-quality claim).\n";
|
|
}
|