feat(#2035): semantic tags + quality score + AI caption+embedding
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 / 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 / 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 / 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 55s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 59s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m38s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m26s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 2m35s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m21s
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled

- Fix: generation_tasks.py passes clip_ai_tags_by_asset to pick_narrative_assets
  so AI tags (weight 2.0) actually participate in narrative mode selection
- Feat: quality_score auto-computation via AssetAnalyzer on ingest
  (worker.calculate_asset_price celery task; fallback 50.0 on failure)
- Feat: smart_match adds ai_semantic dimension (20% weight) using Jaccard
  similarity between asset AI tags (scene/objects/action) and script tags
- Feat: atom_clip caption (10-30 Chinese chars) via Doubao Vision, saved to
  asset_atom_clips.caption (Text column, migration 085)
- Feat: atom_clip embedding vector via Doubao embeddings API, saved to
  asset_atom_clips.embedding (JSON column)
- Chore: remove dead calculate_quality_score_real wrapper
- Tests: 20 new unit tests covering caption parsing, ai_semantic scoring,
  narrative AI tag propagation, score weight changes; update existing tests
  for new fallback dict shape and reweighted dimensions
- Fail-open: tagging/embedding/quality failures never block main flow
This commit is contained in:
saas-backend
2026-09-25 11:06:30 +08:00
parent 1be658f72d
commit 01afc2cf69
18 changed files with 664 additions and 63 deletions
@@ -83,6 +83,23 @@ class SQLAlchemyAssetAtomClipRepository:
models = query.all()
return [self._to_domain(m) for m in models]
def update_caption_embedding(self, clip_id: str, caption: str | None, embedding: list[float] | None = None) -> bool:
"""更新片段的 caption 和 embedding 字段。"""
upd: dict = {}
if caption is not None:
upd["caption"] = caption
if embedding is not None:
upd["embedding"] = embedding
if not upd:
return False
count = (
self.session.query(AssetAtomClipModel)
.filter(AssetAtomClipModel.id == clip_id)
.update(upd)
)
self.session.commit()
return count > 0
def update_ai_tags(self, clip_id: str, ai_tags: dict) -> bool:
"""更新指定片段的 ai_tags 字段."""
count = (
@@ -118,6 +135,8 @@ class SQLAlchemyAssetAtomClipRepository:
clip_index=clip.clip_index,
tags=clip.tags,
ai_tags=clip.ai_tags,
caption=clip.caption,
embedding=clip.embedding,
scene_change_at=clip.scene_change_at,
is_fallback=clip.is_fallback,
created_at=clip.created_at or datetime.now(UTC),
@@ -132,6 +151,9 @@ class SQLAlchemyAssetAtomClipRepository:
duration=model.duration,
clip_index=model.clip_index,
tags=model.tags or [],
ai_tags=getattr(model, "ai_tags", None),
caption=getattr(model, "caption", None),
embedding=getattr(model, "embedding", None),
scene_change_at=model.scene_change_at,
is_fallback=model.is_fallback,
created_at=model.created_at,
@@ -2,6 +2,7 @@ from datetime import UTC, datetime
from typing import Any
from sqlalchemy import (
JSON,
Boolean,
Column,
@@ -841,6 +842,8 @@ class AssetAtomClipModel(Base):
clip_index = Column(Integer, nullable=False)
tags = Column(JSON, nullable=False, default=list)
ai_tags = Column(JSON, nullable=True, default=None)
caption = Column(Text, nullable=True, default=None)
embedding = Column(JSON, nullable=True, default=None)
scene_change_at = Column(Float, nullable=True)
is_fallback = Column(Boolean, nullable=False, default=False)
created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(UTC))