This commit is contained in:
Your Name
2026-10-09 09:36:17 +08:00
parent 88b94c2fc6
commit 51d8fd409e
727 changed files with 3753398 additions and 102 deletions
@@ -11,6 +11,7 @@ use app\common\model\tcm\Diagnosis;
use app\common\model\tcm\DiagnosisAiReport;
use app\common\service\DifyChatService;
use app\common\service\NihaixiaClinicalSkill;
use app\common\service\TcmAncientBooksReference;
use think\facade\Db;
use think\facade\Log;
@@ -1630,7 +1631,7 @@ PROMPT;
'model_label' => self::cleanText($modelLabel, 50),
'report_content' => $content,
'message_id' => self::cleanText($messageId, 191),
'ancient_book_sources_json' => json_encode($ancientBookSources, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '[]',
'ancient_book_sources_json' => json_encode(TcmAncientBooksReference::localizeSources($ancientBookSources), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '[]',
'prompt_version' => self::PROMPT_VERSION,
'case_fingerprint' => (string) $context['fingerprint'],
'generated_by' => $adminId,
@@ -1763,7 +1764,8 @@ PROMPT;
private static function decodeAncientBookSources(mixed $value): array
{
$decoded = is_string($value) ? json_decode($value, true) : null;
return is_array($decoded) && array_is_list($decoded) ? array_values(array_filter($decoded, 'is_array')) : [];
return is_array($decoded) && array_is_list($decoded)
? TcmAncientBooksReference::localizeSources($decoded) : [];
}
/** @return array<string,array<string,mixed>> */
@@ -12,6 +12,7 @@ use app\common\model\dept\Dept;
use app\common\model\tcm\PatientAiReport;
use app\common\service\DifyChatService;
use app\common\service\NihaixiaClinicalSkill;
use app\common\service\TcmAncientBooksReference;
use app\common\service\FileService;
use think\facade\Db;
use think\facade\Log;
@@ -247,8 +248,8 @@ class PatientAiReportLogic extends BaseLogic
'source_snapshot' => $sourceJson,
'source_summary_json' => self::encodeJson($sourceSummary),
'source_diagnosis_ids_json' => self::encodeJson($diagnosisIds),
'ancient_book_sources_json' => self::encodeJson(is_array($upstream['ancient_book_sources'] ?? null)
? $upstream['ancient_book_sources'] : []),
'ancient_book_sources_json' => self::encodeJson(TcmAncientBooksReference::localizeSources(
is_array($upstream['ancient_book_sources'] ?? null) ? $upstream['ancient_book_sources'] : [])),
'source_hash' => $sourceHash,
'prompt_version' => self::PROMPT_VERSION,
'message_id' => self::cleanText($upstream['message_id'] ?? '', 191),
@@ -1110,19 +1111,29 @@ class PatientAiReportLogic extends BaseLogic
}
$seen = [];
foreach ($accumulated as $source) {
$seen[(string) ($source['url'] ?? '')] = true;
$seen[self::ancientBookSourceKey($source)] = true;
}
foreach ($sources as $source) {
if (!is_array($source) || !is_string($source['url'] ?? null) || isset($seen[$source['url']])) {
if (!is_array($source)) {
continue;
}
$seen[$source['url']] = true;
$key = self::ancientBookSourceKey($source);
if (isset($seen[$key])) {
continue;
}
$seen[$key] = true;
$accumulated[] = $source;
if (count($accumulated) >= 12) {
break;
}
}
return $accumulated;
return TcmAncientBooksReference::localizeSources($accumulated);
}
private static function ancientBookSourceKey(array $source): string
{
return (string) ($source['commit'] ?? '') . "\0" . (string) ($source['path'] ?? '')
. "\0" . (string) ($source['line'] ?? '');
}
/** @param array<string,mixed> $snapshot */
@@ -1505,7 +1516,8 @@ class PatientAiReportLogic extends BaseLogic
'source_summary' => $sourceSummary !== [] ? $sourceSummary : self::emptySourceSummary(),
'source_diagnosis_ids' => self::decodeJsonArray($row['source_diagnosis_ids_json'] ?? ''),
'knowledge_source' => NihaixiaClinicalSkill::knowledgeSourceForPromptVersion((string) ($row['prompt_version'] ?? '')),
'ancient_book_sources' => self::decodeJsonArray($row['ancient_book_sources_json'] ?? ''),
'ancient_book_sources' => TcmAncientBooksReference::localizeSources(
self::decodeJsonArray($row['ancient_book_sources_json'] ?? '')),
'prompt_version' => (string) ($row['prompt_version'] ?? ''),
'version' => max((int) ($row['version'] ?? 1), 1),
'generated_time' => $generatedTime,
@@ -5,6 +5,8 @@ declare(strict_types=1);
namespace app\adminapi\logic\tcm;
use app\common\model\auth\Admin;
use app\common\service\NihaixiaClinicalSkill;
use app\common\service\TcmAncientBooksReference;
use app\common\service\prescriptionai\PrescriptionAiAccess as Access;
use app\common\service\prescriptionai\PrescriptionAiCipher as Cipher;
use app\common\service\prescriptionai\PrescriptionAiDoctorSnapshot as DoctorSnapshot;
@@ -298,6 +300,9 @@ final class PrescriptionAiLogic
}
foreach ($results as $result) {
$body = $full ? (new Cipher())->decrypt($result['body_cipher'], 'result:' . $result['batch_id'] . ':' . $result['model_key']) : [];
if ($full) {
$body = self::localizeSourceMetadata($body);
}
unset($result['body_cipher']);
$result['score'] = $result['score'] === null ? null : (float) $result['score'];
$result['herb_score'] = $result['herb_score'] === null ? null : (float) $result['herb_score'];
@@ -321,6 +326,15 @@ final class PrescriptionAiLogic
return $map;
}
/** Existing encrypted results may still contain repository links. */
private static function localizeSourceMetadata(array $body): array
{
$body['knowledge_source'] = NihaixiaClinicalSkill::localizeKnowledgeSource($body['knowledge_source'] ?? null);
$body['ancient_book_sources'] = TcmAncientBooksReference::localizeSources(
is_array($body['ancient_book_sources'] ?? null) ? $body['ancient_book_sources'] : []);
return $body;
}
private static function formatBatch(array $batch, array $models, ?array $rx = null): array
{
$validity = $batch['validity'];
@@ -15,7 +15,6 @@ final class NihaixiaClinicalSkill
{
public const VERSION = 'nihaixia-270e94e-clinical-v1';
public const REPOSITORY_URL = 'https://github.com/coinweth/nihaixia-skill';
public const COMMIT = '270e94e637c4249dc411e54ac6715f21247de3cb';
public const SECTIONS = ['六经速查表', '六经辨证诊断公式', '七步走辨证思维模式'];
@@ -24,12 +23,26 @@ final class NihaixiaClinicalSkill
{
return [
'title' => '倪海厦临床参考 Skill',
'url' => self::REPOSITORY_URL . '/blob/' . self::COMMIT . '/SKILL.md',
'url' => '',
'path' => 'SKILL.md',
'commit' => self::COMMIT,
'sections' => self::SECTIONS,
];
}
/** Remove external links from saved prescription results without changing their attribution. */
public static function localizeKnowledgeSource(mixed $source): ?array
{
if (!is_array($source)) {
return null;
}
$source['url'] = '';
if (($source['commit'] ?? null) === self::COMMIT && empty($source['path'])) {
$source['path'] = 'SKILL.md';
}
return $source;
}
/** Historical reports only claim the reference when their saved version identifies it. */
public static function knowledgeSourceForPromptVersion(string $promptVersion): ?array
{
@@ -12,7 +12,6 @@ final class TcmAncientBooksReference
{
public const COMMIT = 'db0155dc7c42b9c6b3736896661f317c7110038f';
public const INDEX_VERSION = 3;
public const REPOSITORY_URL = 'https://github.com/xiaopangxia/TCM-Ancient-Books';
private const MARKER = '[TCM_ANCIENT_BOOKS:' . self::COMMIT . ']';
/** Modern terms are mapped to searchable historical headings/phrases. */
@@ -136,7 +135,7 @@ final class TcmAncientBooksReference
'path' => $path,
'line' => $line,
'commit' => self::COMMIT,
'url' => self::REPOSITORY_URL . '/blob/' . self::COMMIT . '/' . rawurlencode($path) . '#L' . $line,
'url' => '',
];
if (count($sources) >= $limit) {
break;
@@ -145,6 +144,20 @@ final class TcmAncientBooksReference
return $sources;
}
/** Keep saved citations and excerpts, but never expose an external source URL. */
public static function localizeSources(array $sources): array
{
$localized = [];
foreach ($sources as $source) {
if (!is_array($source)) {
continue;
}
$source['url'] = '';
$localized[] = $source;
}
return $localized;
}
public static function augmentQuery(string $query): string
{
if (str_contains($query, self::MARKER)) {
@@ -189,7 +202,7 @@ final class TcmAncientBooksReference
$lines[] = '《' . $source['title'] . '》' . ($source['chapter'] !== '' ? '「' . $source['chapter'] . '」' : '')
. ' 第' . $source['line'] . '行:' . mb_substr($source['excerpt'], 0, 80, 'UTF-8');
}
$lines[] = '只可引用上述原文;书名、篇章、行号及固定提交链接由系统保存并展示。保持原任务的 JSON 结构与来源编号。';
$lines[] = '只可引用上述原文;书名、篇章、文件名、行号及资料版本由系统保存并展示。保持原任务的 JSON 结构与来源编号。';
return implode("\n", $lines);
}
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace app\common\service\prescriptionai;
use app\common\service\NihaixiaClinicalSkill;
use app\common\service\TcmAncientBooksReference;
use DomainException;
use think\facade\Db;
@@ -345,8 +347,9 @@ final class PrescriptionAiStore
$coverageStatus = in_array($coverageStatus, ['complete', 'full'], true) ? 'complete' : 'partial';
$body = ['report' => $output['report'] ?? [], 'candidate' => $output['candidate'] ?? null,
'comparison' => $comparison, 'coverage' => $coverage, 'usage' => $output['usage'] ?? [],
'knowledge_source' => $output['knowledge_source'] ?? null,
'ancient_book_sources' => $output['ancient_book_sources'] ?? []];
'knowledge_source' => NihaixiaClinicalSkill::localizeKnowledgeSource($output['knowledge_source'] ?? null),
'ancient_book_sources' => TcmAncientBooksReference::localizeSources(
is_array($output['ancient_book_sources'] ?? null) ? $output['ancient_book_sources'] : [])];
$now = time();
$resultId = (int) Db::name('prescription_ai_result')->insertGetId([
'batch_id' => $task['batch_id'], 'model_key' => $task['model_key'],