fix(quality): classification_status 赋枚举值而非裸字符串 + 仓储层防御 (#2079) #2080

Merged
auto-approve-bot merged 2 commits from fix/quality-classification-status-enum into develop 2026-09-28 01:37:21 +08:00
Owner

背景

#2079 队列分流 PR 合入后,跑 backfill_asset_quality 脚本时发现 calculate_asset_quality task 连续报 'str' object has no attribute 'value'(400 条任务全部重试空转)。运维已止血(清空 transcode 队列 + restart worker)。

根因(精确到行)

  • packages/domain/entities.py 中 Asset.classification_status 类型为 ClassificationStatus 枚举(= ClassificationJobStatus 的别名)
  • 仓储 packages/adapters/sqlalchemy_impl/asset_repository.py:172 写库时执行 model.classification_status = asset.classification_status.value,期望枚举
  • 但 task apps/worker/worker_app/tasks/asset_quality_scoring_task.py:99 写的是裸字符串 asset.classification_status = "completed",update() 在 .value 处抛 AttributeError
  • 分类失败分支(except cls_err)也未设置状态,导致 PENDING → 下次重试再跑同一 asset 再次失败(max_retries=1 后仍卡在 failed)

之前我在 PR #2072 合入时验证过 calculate_quality_score/classify_from_analysis 返回枚举正常(那部分确实是修好的),但没覆盖到 task 自己给 classification_status 赋字符串这一段,是验证盲区。

修复

(a) task 赋枚举值(主修复)apps/worker/worker_app/tasks/asset_quality_scoring_task.py

  • 顶部导入 from packages.domain.classification import ClassificationStatus
  • 成功分支:asset.classification_status = "completed" → ClassificationStatus.COMPLETED
  • 失败分支:显式 asset.classification_status = ClassificationStatus.FAILED,避免停留在 PENDING 被反复重试

(b) 仓储层防御(兜底)packages/adapters/sqlalchemy_impl/asset_repository.py

create/update 中对 asset.status.value / asset.classification_status.value 加 hasattr 防御:

  • 是枚举 → 取 .value
  • 已经是 str(未来新代码再误赋字符串)→ 直接 str() 用,不再炸 update
  • 覆盖 create():131/133/149 和 update():175-179 共 4 处

部署步骤

  1. 合入后 watchtower 自动拉新 worker 镜像
  2. 运维等镜像更新后,先小批量验证:
    docker exec xiaoxia-worker-staging python -m scripts.backfill_asset_quality --since-days 7 --dry-run
    docker exec xiaoxia-worker-staging python -m scripts.backfill_asset_quality --since-days 7 --batch-size 20 --sleep 2
    
  3. 观察 worker 日志无 'str' object has no attribute 'value' 报错,且 DB 中 quality_score IS NOT NULL 数量增长后,再全量跑:
    docker exec xiaoxia-worker-staging python -m scripts.backfill_asset_quality --batch-size 50 --sleep 1
    

不做的事

  • 没动 Asset 实体/枚举定义本身
  • 没动 analyzer/分类器纯函数(已验证返回枚举正确)
  • 没改 backfill 脚本(脚本本身没问题)
  • 当前 staging 渲染走 generation 队列不受此 bug 影响,无需回滚 #2079
## 背景 #2079 队列分流 PR 合入后,跑 backfill_asset_quality 脚本时发现 `calculate_asset_quality` task 连续报 `'str' object has no attribute 'value'`(400 条任务全部重试空转)。运维已止血(清空 transcode 队列 + restart worker)。 ## 根因(精确到行) - `packages/domain/entities.py` 中 `Asset.classification_status` 类型为 `ClassificationStatus` 枚举(= `ClassificationJobStatus` 的别名) - 仓储 `packages/adapters/sqlalchemy_impl/asset_repository.py:172` 写库时执行 `model.classification_status = asset.classification_status.value`,期望枚举 - 但 task `apps/worker/worker_app/tasks/asset_quality_scoring_task.py:99` 写的是裸字符串 `asset.classification_status = "completed"`,update() 在 .value 处抛 AttributeError - 分类失败分支(except cls_err)也未设置状态,导致 PENDING → 下次重试再跑同一 asset 再次失败(max_retries=1 后仍卡在 failed) 之前我在 PR #2072 合入时验证过 `calculate_quality_score/classify_from_analysis` 返回枚举正常(那部分确实是修好的),但没覆盖到 task 自己给 classification_status 赋字符串这一段,是验证盲区。 ## 修复 ### (a) task 赋枚举值(主修复)`apps/worker/worker_app/tasks/asset_quality_scoring_task.py` - 顶部导入 `from packages.domain.classification import ClassificationStatus` - 成功分支:`asset.classification_status = "completed"` → `ClassificationStatus.COMPLETED` - 失败分支:显式 `asset.classification_status = ClassificationStatus.FAILED`,避免停留在 PENDING 被反复重试 ### (b) 仓储层防御(兜底)`packages/adapters/sqlalchemy_impl/asset_repository.py` create/update 中对 `asset.status.value` / `asset.classification_status.value` 加 hasattr 防御: - 是枚举 → 取 `.value` - 已经是 str(未来新代码再误赋字符串)→ 直接 str() 用,不再炸 update - 覆盖 create():131/133/149 和 update():175-179 共 4 处 ## 部署步骤 1. 合入后 watchtower 自动拉新 worker 镜像 2. 运维等镜像更新后,先小批量验证: ```bash docker exec xiaoxia-worker-staging python -m scripts.backfill_asset_quality --since-days 7 --dry-run docker exec xiaoxia-worker-staging python -m scripts.backfill_asset_quality --since-days 7 --batch-size 20 --sleep 2 ``` 3. 观察 worker 日志无 `'str' object has no attribute 'value'` 报错,且 DB 中 `quality_score IS NOT NULL` 数量增长后,再全量跑: ```bash docker exec xiaoxia-worker-staging python -m scripts.backfill_asset_quality --batch-size 50 --sleep 1 ``` ## 不做的事 - 没动 Asset 实体/枚举定义本身 - 没动 analyzer/分类器纯函数(已验证返回枚举正确) - 没改 backfill 脚本(脚本本身没问题) - 当前 staging 渲染走 generation 队列不受此 bug 影响,无需回滚 #2079
xiaoxia added 2 commits 2026-09-28 01:27:03 +08:00
fix(asset-repo): status/classification_status 赋值防御,兼容裸字符串 (#2079) - create/update 时对 status/classification_status 两处 .value 调用加 hasattr 防御,已经是 str 时直接使用,避免后续再出现同类 str.value 错误
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 46s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 50s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m29s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m57s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 3m37s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 3m48s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 4m11s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 4m40s
AI Code Review / AI Code Review (pull_request) Successful in 6m40s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 9m34s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 2s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 7m21s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 38s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 38s
66749ea4a5

🚀 预览环境已部署

项目 详情
PR号 #2080
预览链接 https://pr-2080.preview.xiaoxiajianji.com
API环境 staging

💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。

🔄 每次提交新代码后预览环境会自动更新。

🗑️ PR 关闭或合并后,预览环境会自动清理。

🚀 **预览环境已部署** | 项目 | 详情 | |------|------| | PR号 | #2080 | | 预览链接 | [https://pr-2080.preview.xiaoxiajianji.com](https://pr-2080.preview.xiaoxiajianji.com) | | API环境 | staging | > 💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。 > > 🔄 每次提交新代码后预览环境会自动更新。 > > 🗑️ PR 关闭或合并后,预览环境会自动清理。
auto-approve-bot merged commit 1a93a9c00e into develop 2026-09-28 01:37:21 +08:00
auto-approve-bot deleted branch fix/quality-classification-status-enum 2026-09-28 01:37:23 +08:00

🗑️ 预览环境已清理

PR #2080 已关闭或合并,对应的预览环境已被清理。

如有需要,可以重新打开 PR 来重新生成预览环境。

🗑️ **预览环境已清理** PR #2080 已关闭或合并,对应的预览环境已被清理。 > 如有需要,可以重新打开 PR 来重新生成预览环境。
Sign in to join this conversation.