perf(translate): translation_loop 跳过中文源头,省 TMT 配额

中文 RSS 长新闻(原文就是中文)走 TMT 中翻中,纯粹浪费
月配额(500 万字符)且产生无意义译文。前端 commit 6
已经隐藏"译文"板块;本 commit 在后端拦截,从源头不跑翻译。

改动:
- translation_loop SQL 加 WHERE lang_src IS NULL OR NOT LIKE 'zh%'
  - lang_src 为 NULL 时仍走翻译(英文 RSS 没设 language_src 的合法场景)
  - LIKE 'zh%' 覆盖 zh / zh-CN / zh-Hans / zh-TW 等区域码
- translate_article() 函数内加防御性 guard:中文源直接返
  并把 translation_status 改 'n/a',避免反复入队
  (主路径 SQL 过滤已足够,这里是兜底,应对手动 reset status 的情况)

不影响:
- 短新闻(commit 1 已是 translation_status='n/a',根本不进队列)
- 外文 RSS(走翻译)
- 历史已被错误翻译的中文长新闻:保留 translation_status='ok'
  + body_zh_text 中文(空跑产生的) — commit 6 前端已隐藏,
  不影响用户感知;回滚存量不在本 commit 范围(独立 SQL 即可,
  风险与收益需要单独评估)
- enrichment_loop(commit 1 已经能扫到中文源头的 is_short_news,
  长新闻 lang_src=zh 仍能被 enrichment 处理,排版+插图+评论都跑)

范围:仅 backend/app/workers/pipeline.py,+20/-2 行。
This commit is contained in:
xiaji
2026-06-14 20:57:11 +08:00
parent 57784588c8
commit 55e20e923a

View File

@@ -9,7 +9,7 @@ import asyncio
import logging
from datetime import datetime, timezone
from sqlalchemy import select
from sqlalchemy import or_, select
from sqlalchemy.dialects.postgresql import insert as pg_insert
from app.config import settings
@@ -153,6 +153,17 @@ async def translate_article(article_id: int) -> None:
return
if art.translation_status not in ("pending", "failed"):
return
# 防御性 guard:中文源头(原文就是中文)不应走翻译。
# 正常路径 translation_loop SQL 已经过滤(commit 7),这里兜底。
# 直接把 status 改 n/a 避免反复入队。
if art.lang_src and art.lang_src.lower().startswith("zh"):
logger.info(
"translate_article id=%s skipped: lang_src=%s (中文源,无需翻译)",
article_id, art.lang_src,
)
art.translation_status = "n/a"
await session.commit()
return
title = art.title
body_text = (art.body_text or "")[:TRANSLATE_BODY_MAX]
# lang_src 优先级:article.lang_src > source.language_src > "auto"
@@ -322,10 +333,19 @@ async def translation_loop() -> None:
while True:
try:
async with AsyncSessionLocal() as session:
# 中文源头跳过 — 原文就是中文,TMT 中翻中浪费配额。
# lang_src 为 NULL 时不跳过(可能是英文 RSS 没设 language_src,走翻译正确)
# 前端详情页同步隐藏了"译文"板块(commit 6)
rows = (
await session.execute(
select(Article)
.where(Article.translation_status.in_(("pending", "failed")))
.where(
Article.translation_status.in_(("pending", "failed")),
or_(
Article.lang_src.is_(None),
~Article.lang_src.like("zh%"),
),
)
.order_by(Article.fetched_at.asc(), Article.id.asc())
.limit(TRANSLATION_BATCH_SIZE)
)