更新
This commit is contained in:
@@ -29,6 +29,23 @@ analysisParserExpect(is_string($json), 'fixture JSON encodes');
|
||||
$parsed = $parse->invoke(null, $json);
|
||||
analysisParserExpect($parsed === $validPayload, 'plain JSON parses without changing contract');
|
||||
|
||||
foreach (['diagnosis_ad', 'diagnosis_adive'] as $alias) {
|
||||
$misspelled = $validPayload;
|
||||
$misspelled[$alias] = $misspelled['diagnosis_advice'];
|
||||
unset($misspelled['diagnosis_advice']);
|
||||
analysisParserExpect(
|
||||
$parse->invoke(null, json_encode($misspelled, JSON_UNESCAPED_UNICODE)) === $validPayload,
|
||||
"observed {$alias} typo maps to canonical diagnosis field"
|
||||
);
|
||||
}
|
||||
|
||||
$ambiguous = $validPayload;
|
||||
$ambiguous['diagnosis_ad'] = '另一段诊断';
|
||||
analysisParserExpect(
|
||||
$parse->invoke(null, json_encode($ambiguous, JSON_UNESCAPED_UNICODE)) === null,
|
||||
'conflicting diagnosis fields are rejected'
|
||||
);
|
||||
|
||||
$fenced = "说明文字\n```json\n{$json}\n```\n后续文字";
|
||||
analysisParserExpect($parse->invoke(null, $fenced) === $validPayload, 'fenced JSON with surrounding text parses');
|
||||
|
||||
|
||||
@@ -39,4 +39,39 @@ skillExpect(($augmented[1]['content'] ?? '') === '只输出 JSON', 'original sys
|
||||
skillExpect(($augmented[2]['content'] ?? '') === $question, 'original user question is preserved');
|
||||
skillExpect(NihaixiaClinicalSkill::augmentMessages($augmented) === $augmented, 'stream retries do not duplicate the skill');
|
||||
|
||||
$source = NihaixiaClinicalSkill::knowledgeSource();
|
||||
skillExpect($source['commit'] === '270e94e637c4249dc411e54ac6715f21247de3cb', 'metadata pins the complete vendored commit');
|
||||
skillExpect($source['url'] === NihaixiaClinicalSkill::REPOSITORY_URL . '/blob/' . $source['commit'] . '/SKILL.md', 'metadata links directly to the fixed skill document');
|
||||
skillExpect($source['sections'] === ['六经速查表', '六经辨证诊断公式', '七步走辨证思维模式'], 'metadata names only the extracted sections');
|
||||
skillExpect(NihaixiaClinicalSkill::knowledgeSourceForPromptVersion('patient-longitudinal-report-v2') === null, 'legacy prompt versions do not acquire fabricated sources');
|
||||
skillExpect(NihaixiaClinicalSkill::knowledgeSourceForPromptVersion('patient-longitudinal-report-nihaixia-ancient-v4') === $source, 'new ancient-book reports retain the existing skill attribution');
|
||||
|
||||
foreach ([
|
||||
[app\adminapi\logic\tcm\DiagnosisAiLogic::class, ['report_content' => '{}']],
|
||||
[app\adminapi\logic\tcm\PatientAiReportLogic::class, ['report_json' => '{}', 'source_summary_json' => '{"diagnosis_count":2}', 'source_diagnosis_ids_json' => '[11,12]', 'source_snapshot' => 'PRIVATE PATIENT INPUT']],
|
||||
] as [$class, $row]) {
|
||||
$format = new ReflectionMethod($class, 'formatReportRow');
|
||||
$args = $class === app\adminapi\logic\tcm\DiagnosisAiLogic::class ? ['',] : [];
|
||||
$legacy = $format->invoke(null, $row + ['prompt_version' => 'report-v2'], ...$args);
|
||||
$currentVersion = $class === app\adminapi\logic\tcm\DiagnosisAiLogic::class
|
||||
? 'patient-context-case-explain-nihaixia-v3'
|
||||
: 'patient-longitudinal-report-nihaixia-v3';
|
||||
$current = $format->invoke(null, $row + ['prompt_version' => $currentVersion], ...$args);
|
||||
skillExpect($legacy['knowledge_source'] === null, $class . ' omits a claim for old reports');
|
||||
skillExpect($current['knowledge_source'] === $source, $class . ' returns a pinned source for saved skill reports');
|
||||
skillExpect(!str_contains(json_encode($current), 'PRIVATE PATIENT INPUT'), 'public report metadata does not expose the source snapshot');
|
||||
if ($class === app\adminapi\logic\tcm\PatientAiReportLogic::class) {
|
||||
skillExpect($current['source_summary'] === ['diagnosis_count' => 2], 'patient summary comes from the saved report');
|
||||
skillExpect($current['source_diagnosis_ids'] === [11, 12], 'patient source IDs come from the saved report');
|
||||
}
|
||||
}
|
||||
|
||||
$formatDiagnosis = new ReflectionMethod(app\adminapi\logic\tcm\DiagnosisAiLogic::class, 'formatReportRow');
|
||||
$currentSources = ['source_summary' => ['diagnosis_count' => 2], 'source_diagnosis_ids' => [11, 12]];
|
||||
$matching = $formatDiagnosis->invoke(null, ['case_fingerprint' => 'matching', 'report_content' => '{}'], 'matching', $currentSources);
|
||||
$stale = $formatDiagnosis->invoke(null, ['case_fingerprint' => 'old', 'report_content' => '{}'], 'matching', $currentSources);
|
||||
skillExpect($matching['source_diagnosis_ids'] === [11, 12], 'matching report exposes current source IDs');
|
||||
skillExpect($matching['source_summary'] === ['diagnosis_count' => 2], 'matching report exposes current source counts');
|
||||
skillExpect($stale['source_diagnosis_ids'] === [] && $stale['source_summary'] === [], 'stale report does not borrow current patient sources');
|
||||
|
||||
echo "Nihaixia clinical skill contract passed.\n";
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
use app\common\service\ClinicalKnowledgeReference;
|
||||
use app\common\service\NihaixiaClinicalSkill;
|
||||
use app\common\service\TcmAncientBooksReference;
|
||||
|
||||
function ancientExpect(bool $condition, string $message): void
|
||||
{
|
||||
if (!$condition) {
|
||||
fwrite(STDERR, "FAIL: {$message}\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
$indexProperty = new ReflectionProperty(TcmAncientBooksReference::class, 'index');
|
||||
$indexProperty->setValue(null, [
|
||||
'commit' => TcmAncientBooksReference::COMMIT,
|
||||
'index_version' => TcmAncientBooksReference::INDEX_VERSION,
|
||||
'terms' => [
|
||||
'消渴' => [[
|
||||
'title' => '测试古籍', 'chapter' => '消渴篇',
|
||||
'path' => '123-测试古籍.txt', 'line' => 42,
|
||||
'excerpt' => '原文中谈到消渴的历史记载。', 'score' => 15,
|
||||
]],
|
||||
],
|
||||
'chapter_terms' => [
|
||||
'痛风' => [[
|
||||
'title' => '测试古籍二', 'chapter' => '痛风',
|
||||
'path' => '124-测试古籍二.txt', 'line' => 88,
|
||||
'excerpt' => '痛风相关的原文。', 'score' => 12,
|
||||
]],
|
||||
],
|
||||
]);
|
||||
|
||||
$query = '患者糖尿病、高血糖,请分析病症。';
|
||||
$sources = TcmAncientBooksReference::sourcesForText($query);
|
||||
ancientExpect(count($sources) === 1, 'matching patient symptoms select one excerpt');
|
||||
ancientExpect($sources[0]['title'] === '测试古籍' && $sources[0]['chapter'] === '消渴篇', 'book and chapter retained');
|
||||
ancientExpect(str_ends_with($sources[0]['url'], '/123-%E6%B5%8B%E8%AF%95%E5%8F%A4%E7%B1%8D.txt#L42'), 'permalink pins file and line');
|
||||
ancientExpect(TcmAncientBooksReference::sourcesForText('请分析耳鸣。') === [], 'unmatched symptoms have no invented citation');
|
||||
ancientExpect(TcmAncientBooksReference::sourcesForText('患者出现痛风症状。')[0]['line'] === 88, 'unlisted diseases can match indexed chapter headings');
|
||||
|
||||
$augmented = ClinicalKnowledgeReference::augmentQuery($query);
|
||||
ancientExpect(str_contains($augmented, TcmAncientBooksReference::COMMIT), 'query includes ancient-book version');
|
||||
ancientExpect(str_contains($augmented, NihaixiaClinicalSkill::VERSION), 'existing clinical skill remains');
|
||||
ancientExpect(str_ends_with($augmented, $query), 'original patient question remains intact');
|
||||
ancientExpect(ClinicalKnowledgeReference::augmentQuery($augmented) === $augmented, 'retry does not duplicate references');
|
||||
|
||||
$messages = [['role' => 'user', 'content' => $query]];
|
||||
$wire = ClinicalKnowledgeReference::augmentMessages($messages);
|
||||
ancientExpect(count($wire) === 2 && str_contains($wire[0]['content'], '测试古籍'), 'chat receives reference in system message');
|
||||
ancientExpect(ClinicalKnowledgeReference::augmentMessages($wire) === $wire, 'stream retry does not duplicate reference');
|
||||
|
||||
$indexProperty->setValue(null, null);
|
||||
echo "TCM ancient-books reference contract passed.\n";
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
use app\adminapi\logic\tcm\DiagnosisAiLogic;
|
||||
use app\adminapi\logic\tcm\PatientAiReportLogic;
|
||||
use app\common\service\TcmAncientBooksReference;
|
||||
|
||||
function provenanceExpect(bool $condition, string $message): void
|
||||
{
|
||||
if (!$condition) {
|
||||
fwrite(STDERR, "FAIL: {$message}\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
$source = [
|
||||
'title' => '测试古籍', 'chapter' => '消渴篇', 'path' => '123-测试古籍.txt',
|
||||
'line' => 42, 'commit' => TcmAncientBooksReference::COMMIT,
|
||||
'url' => TcmAncientBooksReference::REPOSITORY_URL . '/blob/'
|
||||
. TcmAncientBooksReference::COMMIT . '/123-%E6%B5%8B%E8%AF%95%E5%8F%A4%E7%B1%8D.txt#L42',
|
||||
];
|
||||
$json = json_encode([$source], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
$diagnosisFormat = new ReflectionMethod(DiagnosisAiLogic::class, 'formatReportRow');
|
||||
$diagnosis = $diagnosisFormat->invoke(null, [
|
||||
'report_content' => '{}',
|
||||
'prompt_version' => 'patient-context-case-explain-nihaixia-ancient-v4',
|
||||
'ancient_book_sources_json' => $json,
|
||||
], '');
|
||||
provenanceExpect($diagnosis['ancient_book_sources'] === [$source], 'diagnosis report retains saved book and line');
|
||||
$oldDiagnosis = $diagnosisFormat->invoke(null, ['report_content' => '{}', 'prompt_version' => 'legacy-v2'], '');
|
||||
provenanceExpect($oldDiagnosis['ancient_book_sources'] === [], 'old diagnosis report gets no ancient citation');
|
||||
|
||||
$patientFormat = new ReflectionMethod(PatientAiReportLogic::class, 'formatReportRow');
|
||||
$patient = $patientFormat->invoke(null, [
|
||||
'report_json' => '{}',
|
||||
'prompt_version' => 'patient-longitudinal-report-nihaixia-ancient-v4',
|
||||
'ancient_book_sources_json' => $json,
|
||||
]);
|
||||
provenanceExpect($patient['ancient_book_sources'] === [$source], 'patient report retains saved book and line');
|
||||
$oldPatient = $patientFormat->invoke(null, ['report_json' => '{}', 'prompt_version' => 'legacy-v2']);
|
||||
provenanceExpect($oldPatient['ancient_book_sources'] === [], 'old patient report gets no ancient citation');
|
||||
|
||||
echo "TCM ancient-books report provenance contract passed.\n";
|
||||
Reference in New Issue
Block a user