ip-operator-scenario-seed.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. (() => {
  2. const scope = 'local-guest';
  3. function seedIpOperatorScenario(scenario = 'ready') {
  4. const now = new Date().toISOString();
  5. const user = {
  6. objectId: scope,
  7. username: 'ip-operator-e2e',
  8. displayName: 'IP Operator E2E',
  9. role: 'user',
  10. createdAt: now,
  11. };
  12. clearIpOperatorData();
  13. localStorage.setItem('videoWorkflow.authSession', JSON.stringify({ token: 'ip-operator-e2e-token', user }));
  14. localStorage.setItem('tiktok.currentTab', 'ip-operator');
  15. sessionStorage.clear();
  16. if (scenario === 'empty') return { scenario, profileId: '', planId: '' };
  17. const profileId = `ip_profile_e2e_${scenario}`;
  18. const planId = `ip_plan_e2e_${scenario}`;
  19. const profile = createProfile(profileId, now);
  20. const plan = createPlan({ scenario, profileId, planId, now });
  21. localStorage.setItem(`tiktok.ipOperator.profiles.${scope}`, JSON.stringify([profileId]));
  22. localStorage.setItem(`tiktok.ipOperator.profile.${profileId}.${scope}`, JSON.stringify(profile));
  23. localStorage.setItem(`tiktok.ipOperator.plans.${scope}`, JSON.stringify([planId]));
  24. localStorage.setItem(`tiktok.ipOperator.plan.${planId}.${scope}`, JSON.stringify(plan));
  25. localStorage.setItem(`tiktok.ipOperator.activeProfileId.${scope}`, profileId);
  26. localStorage.setItem(`tiktok.ipOperator.auditTrail.${scope}`, JSON.stringify([{
  27. id: 'ip_audit_e2e_switch',
  28. action: 'switch_profile',
  29. entityType: 'profile',
  30. entityId: profileId,
  31. userId: scope,
  32. createdAt: now,
  33. summary: `Switch current IP profile: ${profileId}`,
  34. }]));
  35. return { scenario, profileId, planId };
  36. }
  37. function clearIpOperatorData() {
  38. for (const key of Object.keys(localStorage)) {
  39. if (
  40. key.startsWith('tiktok.ipOperator.')
  41. || key === 'videoWorkflow.authSession'
  42. || key === 'videoWorkflow.topicPool.items'
  43. ) {
  44. localStorage.removeItem(key);
  45. }
  46. }
  47. }
  48. function createProfile(id, now) {
  49. return {
  50. id,
  51. userId: scope,
  52. name: 'E2E IP Operator Profile',
  53. stage: 'started',
  54. identity: 'Traditional industry consultant for local business owners',
  55. industry: 'Local service business',
  56. targetAudience: 'Owners who need practical IP-driven content and trust conversion',
  57. audiencePainPoints: ['Content looks like advertising', 'No stable topic system'],
  58. productsOrServices: 'IP positioning and content operation consulting',
  59. personalStories: ['Helped a local service team turn cases into short video topics'],
  60. expertise: ['Positioning', 'Topic planning', 'Script diagnosis'],
  61. expressionStyle: ['Direct', 'Practical'],
  62. boundaries: ['No exaggerated income promise'],
  63. goals: ['Build trust', 'Collect consultation leads'],
  64. questionnaire: [],
  65. createdAt: now,
  66. updatedAt: now,
  67. };
  68. }
  69. function createPlan({ scenario, profileId, planId, now }) {
  70. const topics = createTopics();
  71. const scripts = createScripts(topics);
  72. const failed = scenario === 'failed';
  73. const needsInput = scenario === 'needs_input';
  74. return {
  75. id: planId,
  76. userId: scope,
  77. profileId,
  78. status: failed ? 'failed' : needsInput ? 'needs_input' : 'ready',
  79. generationProgress: [
  80. { stage: 'intake_summary', label: 'Intake summary', status: 'completed', durationMs: 120 },
  81. { stage: 'diagnosis', label: 'Diagnosis', status: failed ? 'failed' : 'completed', durationMs: 120, failureCode: failed ? 'llm_malformed_json' : undefined, errorMessage: failed ? 'Malformed JSON in diagnosis stage' : undefined },
  82. ],
  83. qualityCheck: {
  84. overallScore: needsInput ? 64 : failed ? 40 : 88,
  85. positioningClarityScore: needsInput ? 60 : 90,
  86. audienceClarityScore: needsInput ? 62 : 86,
  87. evidenceSupportScore: needsInput ? 50 : 84,
  88. benchmarkTransferScore: needsInput ? 58 : 88,
  89. topicUsabilityScore: needsInput ? 65 : 91,
  90. passed: !needsInput && !failed,
  91. blockingIssues: needsInput ? ['Missing real cases and proof material'] : failed ? ['Generation failed before quality gate'] : [],
  92. improvementSuggestions: needsInput ? ['Add two real client cases before syncing topics'] : ['Validate the first three full scripts with real material.'],
  93. },
  94. intakeSummary: {
  95. identitySummary: 'Local business IP operator',
  96. audienceSummary: 'Traditional business owners',
  97. advantageSummary: 'Can translate business cases into practical content',
  98. businessGoalSummary: 'Build trust and collect consultation leads',
  99. missingInputs: needsInput ? [{ type: 'case', description: 'Add real client before-after cases', priority: 'high' }] : [],
  100. },
  101. diagnosis: failed ? undefined : {
  102. positioningStatement: 'A practical operator who turns local business experience into trusted owner IP content.',
  103. targetAudience: {
  104. summary: 'Owners who need short video content but do not want to become ad accounts.',
  105. painPoints: ['No topic system', 'Content lacks trust', 'Hard to convert'],
  106. },
  107. marketOpportunity: 'Many local businesses have real cases but lack content packaging.',
  108. ipType: 'Operator partner',
  109. personaTags: ['Practical', 'Case-driven', 'Trust-first'],
  110. differentiation: 'Turns benchmark hot points into positioning-matched topics instead of copying formats.',
  111. contentTracks: ['Positioning diagnosis', 'Trust cases', 'Conversion path'],
  112. monetizationPath: 'Consultation and operation service leads.',
  113. risks: ['Avoid exaggerated conversion promises'],
  114. alternativeDirections: ['Focus on one local industry first'],
  115. missingInputs: needsInput ? [{ type: 'case', description: 'Add real proof material', priority: 'high' }] : [],
  116. },
  117. benchmarkAnalyses: failed ? [] : [{
  118. benchmarkId: 'benchmark_e2e_1',
  119. accountName: 'Benchmark Owner Account',
  120. positioning: 'Direct business advice for owners',
  121. contentColumns: ['Pain point diagnosis', 'Case breakdown'],
  122. viralPatterns: ['Question hook', 'Concrete contrast'],
  123. scriptStructure: 'Hook - pain - viewpoint - case - action',
  124. conversionPath: 'Content trust to consultation',
  125. borrowablePoints: ['Direct question hooks', 'Case contrast'],
  126. nonCopyablePoints: ['Personal background', 'Exact industry claims'],
  127. migrationDirection: 'Use the hook logic, replace the case and judgement with this IP assets.',
  128. fitScore: 86,
  129. }],
  130. opportunityMap: failed ? [] : [{
  131. id: 'ip_opp_e2e_1',
  132. type: 'Trust diagnosis',
  133. fitScore: 90,
  134. reason: 'Matches audience pain and available expertise.',
  135. audiencePainPoint: 'Users do not trust product-only content.',
  136. requiredMaterials: ['Before-after case'],
  137. extendableTopics: ['Ad account problem', 'Trust building'],
  138. riskBoundary: 'No guaranteed result claim.',
  139. }],
  140. topics: failed ? [] : topics,
  141. scripts: failed ? [] : scripts,
  142. sevenDayTestPlan: failed ? [] : topics.slice(0, 7).map((topic, index) => ({
  143. day: index + 1,
  144. topicId: topic.id,
  145. topicTitle: topic.title,
  146. testGoal: 'Validate hook clarity and trust response.',
  147. contentType: topic.trafficLayer,
  148. shootingFocus: 'Talk to camera with one real scenario.',
  149. observeMetrics: ['Completion', 'Comment quality', 'Consultation intent'],
  150. reviewQuestions: ['Did users understand the pain?', 'Was the CTA too hard?'],
  151. })),
  152. thirtyDayDirection: failed ? undefined : {
  153. columnStructure: [
  154. { column: 'Positioning diagnosis', ratio: 40, role: 'Trust entry' },
  155. { column: 'Trust content', ratio: 35, role: 'Relationship building' },
  156. { column: 'Conversion path', ratio: 25, role: 'Lead collection' },
  157. ],
  158. weeklyFocus: ['Validate hooks', 'Add case material', 'Test conversion CTA', 'Review topic columns'],
  159. materialDirections: ['Customer doubt list', 'Before-after cases', 'Service process screenshots'],
  160. iterationPath: ['Keep high-comment questions', 'Rewrite weak hooks', 'Promote proven topics'],
  161. },
  162. missingInputs: needsInput ? [{ type: 'case', description: 'Add real client before-after cases', priority: 'high' }] : [],
  163. errorMessage: failed ? 'Generation stopped because diagnosis JSON was malformed.' : undefined,
  164. createdAt: now,
  165. updatedAt: now,
  166. };
  167. }
  168. function createTopics() {
  169. return Array.from({ length: 12 }, (_, index) => ({
  170. id: `ip_topic_e2e_${index + 1}`,
  171. title: index === 0 ? 'Why owner IP should not look like an ad account' : `Owner IP trust topic ${index + 1}`,
  172. column: index % 3 === 0 ? 'Positioning diagnosis' : index % 3 === 1 ? 'Trust content' : 'Conversion path',
  173. trafficLayer: index < 4 ? 'vertical' : index < 8 ? 'conversion' : 'broad',
  174. contentGoal: 'Help the target user understand the trust problem and next action',
  175. priority: index < 4 ? 'high' : index < 9 ? 'medium' : 'low',
  176. fitReason: 'Derived from benchmark hooks and adapted to this IP positioning',
  177. source: 'Benchmark migration',
  178. requiredMaterials: ['Real case', 'Service before-after comparison'],
  179. scriptStatus: index < 3 ? 'full' : 'outline',
  180. riskNote: 'Avoid hard selling and exaggerated claims',
  181. }));
  182. }
  183. function createScripts(topics) {
  184. return topics.map((topic, index) => ({
  185. id: `ip_script_e2e_${index + 1}`,
  186. topicId: topic.id,
  187. type: index < 3 ? 'full' : 'outline',
  188. title: topic.title,
  189. hook: 'If your account keeps introducing products, users will treat it as advertising.',
  190. painPoint: 'The content has information but no trust context.',
  191. viewpoint: 'Owner IP must first transmit judgement, not product inventory.',
  192. caseOrMethod: 'Use one benchmark hook, one real scenario, and one conversion boundary.',
  193. goldenSentence: 'Trust comes before transaction.',
  194. closingCta: 'Save this and check whether your next video is selling or building trust.',
  195. fullScript: index < 3
  196. ? 'Opening: many owner accounts look professional but still fail to convert. Middle: users do not reject business, they reject content that only pushes products. Example: when a local service owner changed from product introductions to decision-making stories, consultation quality improved. Ending: the next video should answer one real customer doubt before mentioning the product.'
  197. : '',
  198. shootingTips: ['Talk to camera', 'Use one service case'],
  199. subtitleHighlights: ['Do not sell first', 'Show judgement first'],
  200. replaceableSlots: ['Insert local case'],
  201. riskNote: 'Do not claim guaranteed conversion.',
  202. }));
  203. }
  204. window.__seedIpOperatorScenario = seedIpOperatorScenario;
  205. })();