const fs = require('fs'); const path = require('path'); // 读取原始数据 const dataPath = path.join(__dirname, 'synthetic_reddit_data.json'); const allPosts = JSON.parse(fs.readFileSync(dataPath, 'utf8')); // 按戏剧性等级分类 const postsByLevel = { high: allPosts.filter(p => p.dramatic_level === 'high'), medium: allPosts.filter(p => p.dramatic_level === 'medium'), low: allPosts.filter(p => p.dramatic_level === 'low') }; console.log('Posts by level:', { high: postsByLevel.high.length, medium: postsByLevel.medium.length, low: postsByLevel.low.length }); // 抽取训练样本 (40条) 和测试样本 (20条) // 保持高/中/低分布比例 ~ 4:1.5:4.5 function samplePosts(posts, count) { const shuffled = [...posts].sort(() => 0.5 - Math.random()); return shuffled.slice(0, count); } const trainingSet = [ ...samplePosts(postsByLevel.high, 16), ...samplePosts(postsByLevel.medium, 6), ...samplePosts(postsByLevel.low, 18) ]; const testSet = [ ...samplePosts(postsByLevel.high.filter(p => !trainingSet.includes(p)), 8), ...samplePosts(postsByLevel.medium.filter(p => !trainingSet.includes(p)), 3), ...samplePosts(postsByLevel.low.filter(p => !trainingSet.includes(p)), 9) ]; // 添加详细标注 function annotatePost(post, split) { // 计算各维度评分 const fieldCompleteness = calculateFieldCompleteness(post); const contentQuality = calculateContentQuality(post); const dramaExtraction = calculateDramaExtraction(post); const jsonCompliance = 100; // 合成数据默认合规 const overallScore = Math.round( fieldCompleteness * 0.25 + contentQuality * 0.30 + dramaExtraction * 0.25 + jsonCompliance * 0.20 ); // 生成标注说明 const annotationNotes = generateAnnotationNotes(post); // 短剧改编建议 const adaptationSuggestions = generateAdaptationSuggestions(post); return { // 原始数据 ...post, // 数据集划分 split: split, // 'train' or 'test' // 人工标注标签 labels: { // 戏剧性判定(人工复核) drama_verified: post.dramatic_score >= 6.0, drama_confidence: post.dramatic_score >= 8.0 ? 'high' : post.dramatic_score >= 6.0 ? 'medium' : 'low', // 短剧价值评级 short_drama_value: post.dramatic_score >= 7.5 ? 'A' : post.dramatic_score >= 6.0 ? 'B' : post.dramatic_score >= 4.0 ? 'C' : 'D', // 改编难度 adaptation_difficulty: post.dramatic_elements.conflict_type === 'relationship_crisis' ? 'easy' : ['family_conflict', 'breakup_drama'].includes(post.dramatic_elements.conflict_type) ? 'easy' : ['workplace_drama', 'trust_issues'].includes(post.dramatic_elements.conflict_type) ? 'medium' : 'hard', // 推荐场景数 recommended_scenes: post.dramatic_score >= 7.0 ? 3 : post.dramatic_score >= 5.0 ? 2 : 1, // 预估时长(秒) estimated_duration: post.dramatic_score >= 7.0 ? 120 : post.dramatic_score >= 5.0 ? 90 : 60 }, // 质量评分 quality_scores: { field_completeness: fieldCompleteness, content_quality: contentQuality, drama_extraction: dramaExtraction, json_compliance: jsonCompliance, overall: overallScore }, // 标注元数据 annotation: { annotator: 'digester-agent', timestamp: new Date().toISOString(), version: '1.0', notes: annotationNotes }, // 短剧改编建议 adaptation: adaptationSuggestions, // 训练用特征 features: { // 数值特征 engagement_score: Math.min(post.num_comments / 2000 + post.score / 10000, 10), controversy_score: (1 - post.upvote_ratio) * 10, // 文本特征 title_length: post.title.length, content_length: post.selftext.length, has_quotation: post.selftext.includes('"'), has_dialogue: /\b(said|told|asked|yelled|screamed)\b/i.test(post.selftext), // 情感特征 emotional_intensity: calculateEmotionalIntensity(post), // 冲突特征 has_clear_conflict: de.protagonist && de.antagonist && de.protagonist !== de.antagonist, conflict_parties_count: countConflictParties(de) } }; } function calculateFieldCompleteness(post) { const required = ['id', 'subreddit', 'title', 'author', 'selftext', 'score', 'num_comments', 'upvote_ratio', 'created_utc', 'permalink', 'drama_elements', 'dramatic_score']; const present = required.filter(f => post[f] !== undefined).length; return Math.round((present / required.length) * 100); } function calculateContentQuality(post) { // 基于互动量和质量的评分 const engagement = Math.min((post.num_comments + post.score / 10) / 500, 10); const recency = 100; // 合成数据默认新鲜 return Math.round((engagement + recency) / 2); } function calculateDramaExtraction(post) { const de = post.drama_elements || {}; let score = 0; if (de.protagonist) score += 20; if (de.antagonist) score += 20; if (de.conflict_type) score += 30; if (de.key_quotes && de.key_quotes.length > 0) score += 20; if (de.dramatic_moments && de.dramatic_moments.length > 0) score += 10; return score; } function calculateEmotionalIntensity(post) { const emotionalWords = ['angry', 'furious', 'devastated', 'heartbroken', 'betrayed', 'shocked', 'livid', 'crushed', 'furious', 'disappointed', 'upset', 'hurt', 'sad', 'mad', 'frustrated']; const text = (post.title + ' ' + post.selftext).toLowerCase(); const count = emotionalWords.reduce((acc, word) => acc + (text.match(new RegExp(word, 'g')) || []).length, 0); return Math.min(count * 2, 10); } function countConflictParties(post) { const parties = new Set(); if (post.dramatic_elements.protagonist) parties.add(post.dramatic_elements.protagonist); if (post.dramatic_elements.antagonist) parties.add(post.dramatic_elements.antagonist); return parties.size; } function generateAnnotationNotes(post) { const notes = []; const de = post.drama_elements || {}; const keyQuotes = de.key_quotes || []; if (post.dramatic_score >= 7.5) { notes.push('High drama potential - priority candidate'); } if (keyQuotes.length >= 2) { notes.push('Multiple quotable lines identified'); } if (post.upvote_ratio < 0.75) { notes.push('Controversial topic - may polarize audience'); } if (post.num_comments > 2000) { notes.push('High engagement - strong audience interest'); } const conflictType = de.conflict_type; if (conflictType && ['relationship_crisis', 'breakup_drama', 'family_conflict'].includes(conflictType)) { notes.push('Universal emotional theme - high relatability'); } return notes; } function generateAdaptationSuggestions(post) { const de = post.drama_elements || {}; const dramaticMoments = de.dramatic_moments || []; // 场景建议 const sceneSuggestions = { 'family_conflict': ['Family dinner scene', 'Living room confrontation'], 'relationship_crisis': ['Bedroom argument', 'Restaurant breakup'], 'workplace_drama': ['Office meeting', 'Break room conversation'], 'wedding_drama': ['Wedding venue', 'Dressing room'], 'trust_issues': ['Coffee shop meeting', 'Phone call scene'], 'inheritance_dispute': ['Lawyer office', 'Family home'], 'default': ['Neutral location', 'Public place'] }; // 角色建议 const characterSuggestions = { 'boyfriend': 'Dominant/confident', 'girlfriend': 'Emotional/expressive', 'husband': 'Defensive/justifying', 'wife': 'Confrontational/determined', 'mother': 'Controlling/worried', 'father': 'Authoritative/dismissive', 'boss': 'Condescending/powerful', 'default': 'Neutral/relatable' }; return { recommended_genre: post.dramatic_level === 'high' ? 'Drama' : post.dramatic_level === 'medium' ? 'Dramedy' : 'Comedy', suggested_scenes: sceneSuggestions[de.conflict_type] || sceneSuggestions['default'], character_directions: { protagonist: characterSuggestions[de.protagonist] || characterSuggestions['default'], antagonist: characterSuggestions[de.antagonist] || characterSuggestions['default'] }, key_moments: dramaticMoments.map(m => ({ type: m.type, timing: m.type === 'reveal' ? '0:30-0:45' : m.type === 'confrontation' ? '0:60-1:15' : m.type === 'climax' ? '1:30-1:45' : '1:00-1:20', visual_emphasis: m.intensity >= 8 ? 'Close-up + slow motion' : m.intensity >= 5 ? 'Medium shot + dramatic lighting' : 'Standard shot' })), dialogue_style: post.subreddit === 'AmITheAsshole' ? 'Confrontational/accusatory' : post.subreddit === 'relationship_advice' ? 'Emotional/vulnerable' : post.subreddit === 'funny' ? 'Casual/humorous' : 'Observational/witty', pacing_notes: post.dramatic_score >= 7.0 ? 'Start with conflict, escalate quickly' : post.dramatic_score >= 5.0 ? 'Build tension gradually' : 'Light, quick pacing' }; } // 生成标注数据 const annotatedTrain = trainingSet.map(p => annotatePost(p, 'train')); const annotatedTest = testSet.map(p => annotatePost(p, 'test')); const dataset = { metadata: { version: '1.0', created_at: new Date().toISOString(), created_by: 'digester-agent', source: 'synthetic_reddit_data_v2', total_posts: allPosts.length, description: 'Annotated Reddit posts for short drama adaptation training' }, statistics: { train_size: annotatedTrain.length, test_size: annotatedTest.length, total_size: annotatedTrain.length + annotatedTest.length, split_ratio: '67:33', dramatic_level_distribution: { train: { high: annotatedTrain.filter(p => p.dramatic_level === 'high').length, medium: annotatedTrain.filter(p => p.dramatic_level === 'medium').length, low: annotatedTrain.filter(p => p.dramatic_level === 'low').length }, test: { high: annotatedTest.filter(p => p.dramatic_level === 'high').length, medium: annotatedTest.filter(p => p.dramatic_level === 'medium').length, low: annotatedTest.filter(p => p.dramatic_level === 'low').length } }, subreddit_distribution: { train: countBySubreddit(annotatedTrain), test: countBySubreddit(annotatedTest) } }, data: { train: annotatedTrain, test: annotatedTest } }; function countBySubreddit(posts) { return posts.reduce((acc, p) => { acc[p.subreddit] = (acc[p.subreddit] || 0) + 1; return acc; }, {}); } // 保存数据集 fs.writeFileSync( path.join(__dirname, 'training_dataset.json'), JSON.stringify(dataset, null, 2) ); console.log('\n=== Training Dataset Generated ==='); console.log(`Train set: ${annotatedTrain.length} samples`); console.log(`Test set: ${annotatedTest.length} samples`); console.log(`Total: ${annotatedTrain.length + annotatedTest.length} samples`); console.log('\nDramatic level distribution (train):'); console.log(' High:', dataset.statistics.dramatic_level_distribution.train.high); console.log(' Medium:', dataset.statistics.dramatic_level_distribution.train.medium); console.log(' Low:', dataset.statistics.dramatic_level_distribution.train.low); console.log('\nSaved to: training_dataset.json');