ip-account-work-report.service.spec.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import { describe, expect, it } from 'vitest';
  2. import {
  3. IpAccountSnapshot,
  4. IpAccountStrategyEvidenceRef,
  5. } from '../models/ip-operator.model';
  6. import { IpAccountWorkReportService } from './ip-account-work-report.service';
  7. describe('IpAccountWorkReportService', () => {
  8. const service = new IpAccountWorkReportService();
  9. it('builds work bundles with comments as supporting evidence only', () => {
  10. const bundles = service.buildWorkEvidenceBundles(snapshotFixture(), rawRefsFixture());
  11. expect(bundles).toHaveLength(1);
  12. expect(bundles[0].workId).toBe('work-1');
  13. expect(bundles[0].workEvidenceRefId).toBe('ev_work_1');
  14. expect(bundles[0].transcriptText).toContain('新手先判断任务');
  15. expect(bundles[0].commentEvidenceRefs.map((item) => item.id)).toEqual(['ev_comment_1', 'ev_comment_2']);
  16. });
  17. it('does not build work bundles when a work has no transcript', () => {
  18. const snapshot = snapshotFixture();
  19. delete snapshot.works[0].transcript;
  20. const bundles = service.buildWorkEvidenceBundles(snapshot, rawRefsFixture());
  21. const reports = service.buildLocalFallbackWorkReports({
  22. now: '2026-07-01T00:00:00.000Z',
  23. bundles,
  24. });
  25. expect(bundles).toEqual([]);
  26. expect(reports).toEqual([]);
  27. expect(service.missingTranscriptWorkIds(snapshot)).toEqual(['work-1']);
  28. });
  29. it('converts parsed work reports into analyzed work report evidence refs', () => {
  30. const reports = service.normalizeParsedWorkReports({
  31. now: '2026-07-01T00:00:00.000Z',
  32. sourceMode: 'llm',
  33. parsedReports: [{
  34. workId: 'work-1',
  35. reports: [{
  36. kind: 'operation',
  37. headline: '新手真实卡点集中',
  38. summary: '评论集中在工具安装顺序和选择成本。',
  39. evidenceSignals: ['收藏高', '评论多为安装问题'],
  40. strategyJudgment: '适合做新手避坑和工具选择栏目。',
  41. supportingEvidenceIds: ['ev_comment_1', 'ev_comment_2'],
  42. confidence: 'high',
  43. gaps: [],
  44. }],
  45. }],
  46. bundles: service.buildWorkEvidenceBundles(snapshotFixture(), rawRefsFixture()),
  47. });
  48. const evidenceRefs = service.toWorkReportEvidenceRefs(reports);
  49. expect(reports[0].id).toBe('work_report_work-1');
  50. expect(evidenceRefs[0]).toMatchObject({
  51. id: 'work_report_work-1',
  52. sourceType: 'owned_work',
  53. sourceId: 'work-1',
  54. analyzed: true,
  55. stage: 'work_report',
  56. });
  57. expect(evidenceRefs[0].reason).toContain('适合做新手避坑');
  58. });
  59. it('builds local fallback work reports without pretending they are LLM analysis', () => {
  60. const reports = service.buildLocalFallbackWorkReports({
  61. now: '2026-07-01T00:00:00.000Z',
  62. bundles: service.buildWorkEvidenceBundles(snapshotFixture(), rawRefsFixture()),
  63. });
  64. const evidenceRefs = service.toWorkReportEvidenceRefs(reports);
  65. expect(reports[0].sourceMode).toBe('local_fallback');
  66. expect(evidenceRefs[0].analyzed).toBe(false);
  67. expect(evidenceRefs[0].reason).toContain('未经过 LLM 独立判断');
  68. });
  69. });
  70. function snapshotFixture(): IpAccountSnapshot {
  71. return {
  72. id: 'snapshot-1',
  73. accountId: 'account-1',
  74. capturedAt: '2026-07-01T00:00:00.000Z',
  75. dataMode: 'data_diagnosis',
  76. warnings: [],
  77. evidenceItemIds: [],
  78. profile: {
  79. nickname: 'FredTalk',
  80. signature: 'Vibe Coding 实操',
  81. followerCount: 1000,
  82. followingCount: 10,
  83. totalFavorited: 2000,
  84. awemeCount: 10,
  85. },
  86. works: [{
  87. id: 'work-1',
  88. accountId: 'account-1',
  89. awemeId: 'aweme-1',
  90. title: '新手小白安装这六个 Skills 就行够了',
  91. desc: '工具安装顺序和选择建议',
  92. publishTime: '2026-06-30T00:00:00.000Z',
  93. transcript: {
  94. text: '新手先判断任务,再选择对应工具。视频逐步讲了安装顺序、常见误区和每个工具的使用边界。',
  95. source: 'asr',
  96. confidence: 'high',
  97. capturedAt: '2026-07-01T00:00:00.000Z',
  98. },
  99. metrics: { playCount: 0, likeCount: 58850, commentCount: 1036, collectCount: 61773, shareCount: 9581 },
  100. interactionScore: 120,
  101. isDeepSampled: true,
  102. structure: { hook: '新手安装清单', topic: '工具选择', style: '教程', cta: '收藏' },
  103. comments: [
  104. { id: 'comment-1', workId: 'work-1', text: '刚开始不知道先用哪个工具', likeCount: 20, capturedAt: '2026-06-30T01:00:00.000Z' },
  105. { id: 'comment-2', workId: 'work-1', text: '安装了很多但是不知道流程', likeCount: 12, capturedAt: '2026-06-30T02:00:00.000Z' },
  106. ],
  107. capturedAt: '2026-07-01T00:00:00.000Z',
  108. }],
  109. };
  110. }
  111. function rawRefsFixture(): IpAccountStrategyEvidenceRef[] {
  112. return [
  113. {
  114. id: 'ev_work_1',
  115. sourceType: 'owned_work',
  116. sourceId: 'work-1',
  117. label: '高互动作品 1',
  118. quote: '新手小白安装这六个 Skills 就行够了',
  119. reason: '互动分 120,赞 58850,评 1036',
  120. stage: 'raw_context',
  121. analyzed: false,
  122. },
  123. {
  124. id: 'ev_comment_1',
  125. sourceType: 'owned_comment',
  126. sourceId: 'comment-1',
  127. label: '重点评论',
  128. quote: '刚开始不知道先用哪个工具',
  129. reason: '原始评论样本',
  130. stage: 'raw_context',
  131. analyzed: false,
  132. },
  133. {
  134. id: 'ev_comment_2',
  135. sourceType: 'owned_comment',
  136. sourceId: 'comment-2',
  137. label: '重点评论',
  138. quote: '安装了很多但是不知道流程',
  139. reason: '原始评论样本',
  140. stage: 'raw_context',
  141. analyzed: false,
  142. },
  143. ];
  144. }