# Changelog: 剧本解析数据格式自动转换 **日期**: 2026-02-08 **类型**: Bug Fix / Enhancement **优先级**: P1 **关联文档**: - [数据完整性测试报告](./2026-02-08-data-integrity-test-report.md) - [剧本解析问题分析](./2026-02-07-screenplay-parse-issues-analysis.md) --- ## 📋 问题背景 ### 问题描述 在 Phase 1 实施过程中,发现 AI 返回的数据格式与 `ScreenplayTagService.store_tags` 方法期望的格式存在差异: #### AI 返回格式(嵌套标签) ```json { "characters": [ { "name": "孙悟空", "tags": { "青年": { "description": "意气风发的年轻猴王", "age_range": "青年", "costume": "金甲战衣、紫金冠" } } } ] } ``` #### Service 期望格式(顶层标签) ```json { "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()` ```python 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` 结构 **调用时机**: ```python 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` 方法 ```python 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` 验证断言 **测试覆盖**: ```python # 验证 dialogue 存储 assert sb1.meta_data.get('dialogue') == "" assert sb2.meta_data.get('dialogue') == "俺老孙今日要大闹天宫!" assert sb3.meta_data.get('dialogue') == "悟空!不可造次!" ``` --- ## 🧪 测试验证 ### 测试命令 ```bash 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 查询语法 ```python # 查询包含对话的分镜 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 **文档更新**: ✅ 已完成