Files
akmon/doc_zhipao/SCORING_CRITERIA_JSON_MIGRATION.md
2026-01-20 08:04:15 +08:00

6.5 KiB
Raw Permalink Blame History

评分标准JSON结构化重构方案

问题分析

当前设计缺陷:

  1. UI层:使用结构化对象 [{min_score, max_score, description}]
  2. 数据库层:使用 \n 分隔的字符串存储在 instructions 字段
  3. 数据丢失min_scoremax_score 等结构化信息在保存时完全丢失

解决方案

方案一:使用现有 scoring_criteria 字段(推荐)

1. 数据库结构调整

-- 第一步:将 scoring_criteria 字段改为 JSONB 类型(如果不是的话)
ALTER TABLE public.ak_training_projects 
  ALTER COLUMN scoring_criteria TYPE JSONB USING scoring_criteria::jsonb;

2. 新的JSON数据结构

{
  "criteria": [
    {
      "min_score": 90,
      "max_score": 100,
      "description": "优秀:动作标准,节奏控制完美,表现卓越"
    },
    {
      "min_score": 80,
      "max_score": 89,
      "description": "良好:动作较为标准,节奏控制良好"
    },
    {
      "min_score": 70,
      "max_score": 79,
      "description": "及格:基本掌握动作要领,需要改进"
    },
    {
      "min_score": 0,
      "max_score": 69,
      "description": "不及格:动作不标准,需要重新练习"
    }
  ],
  "scoring_method": "comprehensive", // 综合评分方式
  "weight_distribution": {
    "technique": 0.4,    // 技术动作权重 40%
    "effort": 0.3,       // 努力程度权重 30%
    "improvement": 0.3   // 进步幅度权重 30%
  }
}

3. 前端代码重构

加载数据时:

// 修改 loadProjectData 函数
function loadProjectData() {
    // ...现有代码...
    
    // 处理评分标准 - 使用新的JSON结构
    let scoringCriteria: Array<UTSJSONObject> = []
    const criteriaData = safeGet(projectData, 'scoring_criteria', null)
    
    if (criteriaData && typeof criteriaData === 'object') {
        // 新的JSON格式
        const criteria = safeGet(criteriaData, 'criteria', [])
        if (criteria instanceof Array) {
            scoringCriteria = criteria.map((item: any) => ({
                min_score: item.min_score?.toString() || '',
                max_score: item.max_score?.toString() || '',
                description: item.description || ''
            } as UTSJSONObject))
        }
    } else if (typeof criteriaData === 'string') {
        // 兼容旧的 \n 分隔格式
        const instructionSteps = criteriaData.split('\n').filter((step: string) => step.trim().length > 0)
        scoringCriteria = instructionSteps.map((step: string, index: number) => ({
            min_score: (index * 20).toString(),
            max_score: ((index + 1) * 20).toString(),
            description: step.trim()
        } as UTSJSONObject))
    }
    
    if (scoringCriteria.length === 0) {
        scoringCriteria = [{ min_score: '', max_score: '', description: '' } as UTSJSONObject]
    }
    
    // ...其他代码...
}

保存数据时:

// 修改 updateProject 和 saveDraft 函数
function updateProject() {
    // ...现有代码...
    
    const scoringCriteria = getScoringCriteria()
    const scoringCriteriaJson = {
        criteria: scoringCriteria.map((criteria: UTSJSONObject) => ({
            min_score: parseInt(safeGet(criteria, 'min_score', '0')),
            max_score: parseInt(safeGet(criteria, 'max_score', '100')),
            description: safeGet(criteria, 'description', '')
        })).filter(item => item.description.trim().length > 0),
        scoring_method: "comprehensive",
        weight_distribution: {
            technique: 0.4,
            effort: 0.3,
            improvement: 0.3
        }
    }
    
    const { data, error } = await supaClient
        .from('ak_training_projects')
        .update({
            // ...其他字段...
            scoring_criteria: scoringCriteriaJson,
            instructions: '', // 清空旧的instructions字段避免混淆
            updated_at: new Date().toISOString()
        })
        .eq('id', projectId.value)
        .execute()
    
    // ...其他代码...
}

方案二:添加新字段(备选方案)

如果不想修改现有字段,可以添加新字段:

-- 添加新的JSONB字段
ALTER TABLE public.ak_training_projects 
  ADD COLUMN scoring_criteria_json JSONB;

-- 创建索引以提高查询性能
CREATE INDEX idx_ak_training_projects_scoring_criteria 
  ON public.ak_training_projects USING GIN (scoring_criteria_json);

数据迁移脚本

第一步:备份现有数据

-- 创建备份表
CREATE TABLE ak_training_projects_backup AS 
SELECT * FROM ak_training_projects;

第二步:迁移现有数据

-- 将现有的 instructions 数据转换为 JSON 格式
UPDATE ak_training_projects 
SET scoring_criteria = jsonb_build_object(
    'criteria', 
    ARRAY(
        SELECT jsonb_build_object(
            'min_score', (ROW_NUMBER() OVER () - 1) * 20,
            'max_score', ROW_NUMBER() OVER () * 20,
            'description', trim(instruction)
        )
        FROM unnest(string_to_array(instructions, E'\n')) AS instruction
        WHERE trim(instruction) != ''
    ),
    'scoring_method', 'comprehensive',
    'weight_distribution', jsonb_build_object(
        'technique', 0.4,
        'effort', 0.3,
        'improvement', 0.3
    )
)
WHERE instructions IS NOT NULL AND instructions != '';

第三步:验证迁移结果

-- 验证JSON结构
SELECT 
    id, 
    title,
    scoring_criteria->'criteria' as criteria_array,
    jsonb_array_length(scoring_criteria->'criteria') as criteria_count
FROM ak_training_projects 
WHERE scoring_criteria IS NOT NULL
LIMIT 5;

优势对比

特性 旧方案 (\n分隔) 新方案 (JSON)
数据结构 平铺字符串 结构化对象
分数范围 丢失 保留
查询能力 有限 强大
扩展性 困难 容易
数据完整性 优秀
AI分析支持 不支持 原生支持

实施建议

  1. 测试环境先行:在测试环境完成迁移和测试
  2. 渐进式部署:保持向后兼容,支持新旧两种格式
  3. 数据校验:迁移后进行全面的数据完整性检查
  4. 性能优化为JSONB字段创建合适的索引

长期收益

  1. 更好的数据查询:可以直接查询特定分数范围的标准
  2. AI集成友好结构化数据更容易被AI系统理解和处理
  3. 扩展性强:未来可以轻松添加新的评分维度
  4. 数据一致性避免了UI和数据库层的数据结构不匹配问题