This commit is contained in:
wyn
2026-05-26 19:02:37 +08:00
parent 3d6cfaaed1
commit 1fcd6a129d
4 changed files with 81 additions and 66 deletions

View File

@@ -21,28 +21,32 @@ class ReferenceCheckService
const AM_STATUS_FAIL = 2;
const AM_STATUS_RUNNING = 3;
/** 引用校对进度(按 reference_no 分组聚合后的对外状态) */
const PROGRESS_PENDING = 0; // 待校验:分组内全部明细 status=0
const PROGRESS_CHECKING = 1; // 校对中:分组内部分明细已结束、部分仍为 0
const PROGRESS_COMPLETED = 2; // 校对完成:分组内全部明细 status=1
const PROGRESS_FAILED = 3; // 校对失败:分组内全部明细已结束,且至少 1 条 status=2
/**
* 引用校对状态生命周期顺序0→1→2→3 = 待→进行→完成→失败)
*
* 这套常量在两个维度共用:
* - 单条明细article_reference_check_result.status只会取 {0, 2, 3} —— 明细不会出现"校对中"
* - 分组(按 reference_no 聚合后的 progress_status四个值都会用 —— 1=部分跑完、部分仍为 0
*/
const PROGRESS_PENDING = 0; // 待校验:明细 status=0分组内全部明细 status=0
const PROGRESS_CHECKING = 1; // 校对中:仅分组层 —— 部分明细已结束、部分仍为 0
const PROGRESS_COMPLETED = 2; // 校对完成:明细 status=2分组内全部明细 status=2
const PROGRESS_FAILED = 3; // 校对失败:明细 status=3分组内全部跑完、≥1 条 status=3
/** 整篇文章的引用校对状态(对外整体状态,用于"开始/重置"按钮分流) */
const ARTICLE_PROGRESS_NONE = 0; // 还没有任何校对记录
const ARTICLE_PROGRESS_RUNNING = 1; // 至少 1 条 status=0队列里还有未跑完的
const ARTICLE_PROGRESS_RUNNING = 1; // 至少 1 条明细 status=0队列里还有未跑完的
const ARTICLE_PROGRESS_COMPLETED = 2; // 所有明细 status != 0全部已完成或失败
/**
* 单条校对明细的对外状态(getProgressByPArticleId 返回的 records[i].status
* 单条校对明细的状态(DB 字段 article_reference_check_result.status
*
* DB 里 article_reference_check_result.status 只有 0/1/2 三种值;
* RECORD_PROCESSING 是基于 Redis 队列锁 :status='processing' 的瞬时态,
* 并不持久化。worker 进入 LLM 调用期间 DB.status 仍是 0需要靠队列锁识别。
* 这里只列实际写入 DB 的三种值。"校对中"(值 1是分组层专用明细不会出现。
* 数值与 PROGRESS_* 对齐(同一套语义),方便前端/后端混用。
*/
const RECORD_PENDING = 0; // 待校对,已入队但还没被 worker 拾起
const RECORD_COMPLETED = 1; // 校对完成
const RECORD_FAILED = 2; // 校对失败
const RECORD_PROCESSING = 3; // 处理中worker 正在跑 LLMRedis :status='processing'
const RECORD_COMPLETED = 2; // 校对完成
const RECORD_FAILED = 3; // 校对失败
/** LLM 评分confidence通过阈值>= 该值视为"通过" */
const PASS_CONFIDENCE_THRESHOLD = 0.65;
@@ -187,7 +191,7 @@ class ReferenceCheckService
$rows = Db::name('article_reference_check_result')
->where('article_id', $articleId)
->where('status', 1)
->where('status', self::RECORD_COMPLETED)
->where('confidence', '<=', 0.65)
->orderRaw('rand()')
->limit(2)
@@ -399,14 +403,14 @@ class ReferenceCheckService
foreach ($rows as $row) {
$st = intval($row['status']);
if ($st === 0) {
if ($st === self::RECORD_PENDING) {
$pending++;
continue;
}
if ($st === 2 || ($st === 1 && intval($row['is_match']) === 0)) {
if ($st === self::RECORD_FAILED || ($st === self::RECORD_COMPLETED && intval($row['is_match']) === 0)) {
$hasFail = true;
}
if ($st === 1) {
if ($st === self::RECORD_COMPLETED) {
$done++;
}
}
@@ -595,8 +599,8 @@ class ReferenceCheckService
*
* 每条参考文献按其明细 status 分布落桶(互斥):
* pending —— 组内任一明细 status=0含部分跑完的"校对中"也归此桶)
* done —— 组内全部明细 status=1
* failed —— 组内全部明细已结束、至少 1 条 status=2
* done —— 组内全部明细 status=2(完成)
* failed —— 组内全部明细已结束、至少 1 条 status=3(失败)
*
* pending + done + failed = totalprogress_percent = (done + failed) / total。
* 分组明细请走 getProgressByPArticleId控制器 referenceCheckProgressAI
@@ -614,8 +618,8 @@ class ReferenceCheckService
// 50 条参考文献 → 返回 50 行PHP 走一次循环分桶即可
$rows = Db::name('article_reference_check_result')
->field('reference_no'
. ', SUM(CASE WHEN status = 0 THEN 1 ELSE 0 END) AS pending_cnt'
. ', SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END) AS failed_cnt')
. ', SUM(CASE WHEN status = ' . self::RECORD_PENDING . ' THEN 1 ELSE 0 END) AS pending_cnt'
. ', SUM(CASE WHEN status = ' . self::RECORD_FAILED . ' THEN 1 ELSE 0 END) AS failed_cnt')
->where('p_article_id', $pArticleId)
->group('reference_no')
->select();
@@ -668,19 +672,19 @@ class ReferenceCheckService
/**
* 按 p_article_id 查整篇引用校对进度,按 reference_no 分组聚合状态,并展开每条明细。
*
* 单条 article_reference_check_result.status
* 0 = 待校验 1 = 校对完成 2 = 校对失败
* 状态映射统一遵循"生命周期顺序"PROGRESS_* / RECORD_* 取值一致)
* 0 = 待校验 1 = 校对中(仅分组层) 2 = 校对完成 3 = 校对失败
*
* 分组reference_no状态返回字段 status,数值类型)
* 0 = PROGRESS_PENDING 待校验 分组内全部明细 status=0
* 1 = PROGRESS_CHECKING 校对中 分组内部分明细已结束、部分仍为 0
* 2 = PROGRESS_COMPLETED 校对完成:分组内全部明细 status=1
* 3 = PROGRESS_FAILED 校对失败:分组内全部明细已结束且至少 1 条 status=2
* 分组reference_no状态返回字段 progress_status
* - 0 = PROGRESS_PENDING 分组内全部明细 status=0
* - 1 = PROGRESS_CHECKING 分组内部分明细已结束、部分仍为 0(明细不会出现此值)
* - 2 = PROGRESS_COMPLETED 分组内全部明细 status=2
* - 3 = PROGRESS_FAILED 分组内全部明细已结束且至少 1 条 status=3
*
* 每个分组还会展开 records 子数组,给出该 reference_no 下每条 check 明细的
* - status(同上 0/1/2
* - confidence 评分
* - is_passconfidence >= PASS_CONFIDENCE_THRESHOLD 视为通过
* records[i] 字段
* - status 0=待校验 2=完成 3=失败(与分组同一套数值含义,不会出现 1
* - confidence LLM 评分
* - is_pass confidence >= PASS_CONFIDENCE_THRESHOLD 视为通过
*
* @return array{p_article_id:int, total_groups:int, summary:array, list:array}
*/
@@ -697,12 +701,12 @@ class ReferenceCheckService
->order('reference_no asc, id asc')
->select();
// summary 用数值键0/1/2/3 对应 PROGRESS_* 常量
// summary 用字符串键,避免数值下标看不出含义;同时保留数值键和 PROGRESS_* 常量对照
$summary = [
self::PROGRESS_PENDING => 0,
self::PROGRESS_CHECKING => 0,
self::PROGRESS_COMPLETED => 0,
self::PROGRESS_FAILED => 0,
'pending' => 0, // PROGRESS_PENDING = 0
'checking' => 0, // PROGRESS_CHECKING = 1
'completed' => 0, // PROGRESS_COMPLETED = 2
'failed' => 0, // PROGRESS_FAILED = 3
];
if (empty($rows)) {
return [
@@ -737,11 +741,12 @@ class ReferenceCheckService
$groups[$refNo]['total']++;
$st = intval($this->arrGet($row, 'status', 0));
if ($st === 0) {
// record 仅存 {0=待校验, 2=完成, 3=失败};不会出现 1校对中
if ($st === self::RECORD_PENDING) {
$groups[$refNo]['pending']++;
} elseif ($st === 1) {
} elseif ($st === self::RECORD_COMPLETED) {
$groups[$refNo]['done']++;
} elseif ($st === 2) {
} elseif ($st === self::RECORD_FAILED) {
$groups[$refNo]['failed']++;
}
@@ -778,22 +783,27 @@ class ReferenceCheckService
$pass = $g['pass'];
if ($pending === $total) {
$status = self::PROGRESS_PENDING;
$progressStatus = self::PROGRESS_PENDING;
} elseif ($pending === 0) {
$status = $failed > 0 ? self::PROGRESS_FAILED : self::PROGRESS_COMPLETED;
$progressStatus = $failed > 0 ? self::PROGRESS_FAILED : self::PROGRESS_COMPLETED;
} else {
$status = self::PROGRESS_CHECKING;
$progressStatus = self::PROGRESS_CHECKING;
}
// 整体通过校验:分组已全部完成(无 pending、无 failed且每条 confidence >= 0.65
$g['is_pass'] = (
$status === self::PROGRESS_COMPLETED
$progressStatus === self::PROGRESS_COMPLETED
&& $total > 0
&& $pass === $total
);
$summary[$status]++;
$g['status'] = $status;
switch ($progressStatus) {
case self::PROGRESS_PENDING: $summary['pending']++; break;
case self::PROGRESS_CHECKING: $summary['checking']++; break;
case self::PROGRESS_COMPLETED: $summary['completed']++; break;
case self::PROGRESS_FAILED: $summary['failed']++; break;
}
$g['progress_status'] = $progressStatus;
$list[] = $g;
}
@@ -988,7 +998,7 @@ class ReferenceCheckService
$q->where('status', $status);
}
if ($onlyMismatch) {
$q->where('status', 1)->where('is_match', 0);
$q->where('status', self::RECORD_COMPLETED)->where('is_match', 0);
}
return $q->order('am_id asc, cite_tag_start asc, reference_no asc')->select();
}
@@ -1018,7 +1028,7 @@ class ReferenceCheckService
foreach ($this->listByArticle($articleId, -1) as $r) {
$stats['total']++;
if (intval($r['status']) === 0) {
if (intval($r['status']) === self::RECORD_PENDING) {
$stats['pending']++;
} elseif (intval($r['is_match']) === 1) {
$stats['match']++;
@@ -1078,14 +1088,14 @@ class ReferenceCheckService
}
/**
* @param array $rows status=1 的检测结果
* @param array $rows 已校对完成(status=RECORD_COMPLETED但 is_match=0 的检测结果
* @return array<int, array> am_id => indexed bad map
*/
private function indexBadResults($rows)
{
$byAm = [];
foreach ($rows as $row) {
if (intval($row['status']) !== 1 || intval($row['is_match']) === 1) {
if (intval($row['status']) !== self::RECORD_COMPLETED || intval($row['is_match']) === 1) {
continue;
}
$amId = intval($row['am_id']);
@@ -1589,7 +1599,7 @@ class ReferenceCheckService
if ($contentA === '' || $contentB === '') {
$this->updateCheckResult($checkId, [
'status' => 2,
'status' => self::RECORD_FAILED,
'error_msg' => 'Missing article_main.content or refer_text',
]);
throw new \RuntimeException('Missing article_main.content or refer_text');
@@ -1601,13 +1611,13 @@ class ReferenceCheckService
$confidence = floatval(isset($llmResult['confidence']) ? $llmResult['confidence'] : 0);
$reason = isset($llmResult['reason']) ? $llmResult['reason'] : '';
// LLM 通讯失败:写 status=2(校对失败) + error_msg抛异常让队列 worker 走 release(30) 重试;
// 重试 3 次后 ReferenceCheck::markFailed 会保持 status=2 收尾
// LLM 通讯失败:写 status=RECORD_FAILED(3) + error_msg抛异常让队列 worker 走 release(30) 重试;
// 重试 3 次后 ReferenceCheck::markFailed 会保持 status=3 收尾
if ($requestFailed) {
$this->updateCheckResult($checkId, [
'confidence' => $confidence,
'reason' => $reason,
'status' => 2,
'status' => self::RECORD_FAILED,
'error_msg' => $reason,
]);
$this->clearReferenceCheckQueueLock($checkId);
@@ -1619,7 +1629,7 @@ class ReferenceCheckService
'is_match' => $canSupport ? 1 : 0,
'confidence' => $confidence,
'reason' => $reason,
'status' => 1,
'status' => self::RECORD_COMPLETED,
'error_msg' => '',
]);