You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

6.7 KiB

Changelog: 剧本解析数据格式自动转换

日期: 2026-02-08
类型: Bug Fix / Enhancement
优先级: P1
关联文档:


📋 问题背景

问题描述

在 Phase 1 实施过程中,发现 AI 返回的数据格式与 ScreenplayTagService.store_tags 方法期望的格式存在差异:

AI 返回格式(嵌套标签)

{
  "characters": [
    {
      "name": "孙悟空",
      "tags": {
        "青年": {
          "description": "意气风发的年轻猴王",
          "age_range": "青年",
          "costume": "金甲战衣、紫金冠"
        }
      }
    }
  ]
}

Service 期望格式(顶层标签)

{
  "characters": [...],
  "character_tags": {
    "孙悟空": [
      {
        "tag_label": "青年",
        "description": "意气风发的年轻猴王",
        "meta_data": {"age_range": "青年", "costume": "金甲战衣、紫金冠"}
      }
    ]
  }
}

影响范围

  • 标签数据无法正确存储
  • store_tags 日志显示:角色标签=0, 场景标签=0, 道具标签=0
  • 集成测试需手动构造双重格式数据

解决方案

1. 自动标签格式转换

文件: server/app/services/screenplay_service.py

新增方法: _transform_ai_tags_format()

def _transform_ai_tags_format(self, parsed_data: Dict[str, Any]) -> Dict[str, Any]:
    """转换AI返回的嵌套标签格式为Service期望的顶层格式
    
    转换逻辑:
    1. 遍历 characters/locations/props 中的嵌套 tags
    2. 提取 tag_label、description、meta_data
    3. 构造顶层 character_tags/location_tags/prop_tags
    """

关键特性

  • 自动解析嵌套 tags 字典
  • 提取 description 字段到顶层
  • 其余字段归并到 meta_data
  • 保留原始 characters/locations/props 结构

调用时机

async def store_parsed_elements(...):
    # 0. 数据格式预处理:转换AI嵌套标签格式为Service期望格式
    parsed_data = self._transform_ai_tags_format(parsed_data)
    logger.info("标签格式转换完成: screenplay_id=%s", screenplay_id)

2. dialogue 字段存储策略

问题: Storyboard 模型没有 dialogue 字段

解决方案: 存储到 meta_data JSONB 字段

修改位置: _create_storyboards_from_ai 方法

storyboard = Storyboard(
    # ...
    meta_data={
        **sb_data.get('meta_data', {}),
        # 存储对话信息(Storyboard模型没有dialogue字段)
        'dialogue': sb_data.get('dialogue', ''),
        # 冗余存储元素信息(用于快速查询)
        'screenplay_id': str(screenplay_id),
        # ...
    }
)

优点

  • 无需修改数据库 Schema
  • 保持向后兼容
  • 易于查询和过滤

3. 测试用例简化

文件: server/tests/integration/test_data_integrity.py

变更

  • 移除手动构造的顶层标签字段 (character_tags, location_tags, prop_tags)
  • 仅保留 AI 原始嵌套格式
  • 新增 dialogue 验证断言

测试覆盖

# 验证 dialogue 存储
assert sb1.meta_data.get('dialogue') == ""
assert sb2.meta_data.get('dialogue') == "俺老孙今日要大闹天宫!"
assert sb3.meta_data.get('dialogue') == "悟空!不可造次!"

🧪 测试验证

测试命令

cd server
docker compose exec -T app pytest \
  tests/integration/test_data_integrity.py::TestScreenplayDataIntegrity::test_store_parsed_elements_complete_integrity \
  -v -s

测试结果

标签格式转换完成: screenplay_id=019c3c67-4650-7ca3-8664-94547a96b3e8
标签存储完成: 角色标签=3, 场景标签=3, 道具标签=3

✅ 角色记录验证通过
✅ 场景记录验证通过
✅ 道具记录验证通过
✅ 分镜记录验证通过
   - 分镜1: 水帘洞外景 | WIDE_SHOT | DOLLY | 对话: ""
   - 分镜2: 孙悟空登场 | MEDIUM_SHOT | STATIC | 对话: "俺老孙今日要大闹天宫!"
   - 分镜3: 天宫对峙 | FULL_SHOT | PAN | 对话: "悟空!不可造次!"
✅ 分镜元素关联验证通过
✅ 标签数据结构验证通过
🎉 数据完整性测试通过!

验证指标

  • 标签转换正确:角色标签=3, 场景标签=3, 道具标签=3
  • 标签关联完整:每个元素的 tags relationship 包含正确标签
  • dialogue 存储成功:所有分镜的 meta_data['dialogue'] 正确

📊 技术影响分析

代码变更统计

文件 变更类型 行数
screenplay_service.py 新增方法 +95
screenplay_service.py 修改调用 +2
screenplay_service.py dialogue 存储 +2
test_data_integrity.py 简化测试数据 -41
test_data_integrity.py 新增断言 +3

性能影响

  • 格式转换: O(n) 复杂度,n = 元素数量 + 标签数量
  • 内存开销: 临时字典构造,完成后自动释放
  • 数据库查询: 无额外查询,仅在存储前转换

向后兼容性

完全兼容

  • 保留原始 AI 数据结构
  • 仅添加顶层字段,不修改原有字段
  • 不影响现有 API 接口

🔗 关联问题

已解决

  • P1-02: AI Prompt 文档与实际实现不一致(数据格式部分)
  • 数据完整性测试 - 待办 1: dialogue 字段缺失

待解决(Phase 2)

  • P1-01: project_resources 表未关联
  • 实现 _sync_storyboard_resources() 方法

📝 开发者注意事项

使用建议

  1. AI Prompt 设计: 可继续使用嵌套标签格式,无需调整
  2. 测试数据: 使用 AI 原始格式即可,无需手动转换
  3. 查询 dialogue: 使用 JSONB 查询语法
    # 查询包含对话的分镜
    stmt = select(Storyboard).where(
        Storyboard.meta_data['dialogue'].astext != ''
    )
    

迁移指南

现有测试代码

  • 可移除手动构造的 character_tags 等字段
  • Service 会自动处理格式转换

API 调用

  • 无需修改,内部自动转换

总结

本次修复实现了 AI 数据格式与 Service 期望格式的无缝桥接,消除了手动格式转换的需要,提升了代码的可维护性和测试的简洁性。

核心价值

  1. 自动化数据格式转换
  2. 解决 dialogue 字段存储问题
  3. 简化测试用例编写
  4. 保持向后兼容

下一步

  • Phase 2: 实现 project_resources 表关联逻辑
  • 持续监控 AI 输出格式变化

审核: Pending
部署: Pending
文档更新: 已完成