v2er-insight 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/README.md +215 -0
  2. package/dist/cli/commands/ai.d.ts +13 -0
  3. package/dist/cli/commands/ai.js +153 -0
  4. package/dist/cli/commands/analyze.d.ts +13 -0
  5. package/dist/cli/commands/analyze.js +80 -0
  6. package/dist/cli/commands/config.d.ts +43 -0
  7. package/dist/cli/commands/config.js +267 -0
  8. package/dist/cli/commands/fetch.d.ts +13 -0
  9. package/dist/cli/commands/fetch.js +150 -0
  10. package/dist/cli/commands/index.d.ts +10 -0
  11. package/dist/cli/commands/index.js +22 -0
  12. package/dist/cli/commands/run.d.ts +23 -0
  13. package/dist/cli/commands/run.js +52 -0
  14. package/dist/cli/commands/show.d.ts +13 -0
  15. package/dist/cli/commands/show.js +154 -0
  16. package/dist/cli/index.d.ts +6 -0
  17. package/dist/cli/index.js +107 -0
  18. package/dist/cli/types.d.ts +58 -0
  19. package/dist/cli/types.js +6 -0
  20. package/dist/cli/utils/error.d.ts +6 -0
  21. package/dist/cli/utils/error.js +18 -0
  22. package/dist/cli/utils.d.ts +20 -0
  23. package/dist/cli/utils.js +48 -0
  24. package/dist/cli/workflow/orchestrator.d.ts +15 -0
  25. package/dist/cli/workflow/orchestrator.js +144 -0
  26. package/dist/cli/workflow/recovery.d.ts +10 -0
  27. package/dist/cli/workflow/recovery.js +134 -0
  28. package/dist/cli/workflow/state.d.ts +19 -0
  29. package/dist/cli/workflow/state.js +45 -0
  30. package/dist/cli/workflow/types.d.ts +60 -0
  31. package/dist/cli/workflow/types.js +3 -0
  32. package/dist/config/defaults.d.ts +48 -0
  33. package/dist/config/defaults.js +42 -0
  34. package/dist/config/index.d.ts +16 -0
  35. package/dist/config/index.js +21 -0
  36. package/dist/config/path.d.ts +11 -0
  37. package/dist/config/path.js +28 -0
  38. package/dist/config/proxy.d.ts +16 -0
  39. package/dist/config/proxy.js +39 -0
  40. package/dist/config/storage.d.ts +23 -0
  41. package/dist/config/storage.js +85 -0
  42. package/dist/config/types/ai.d.ts +31 -0
  43. package/dist/config/types/ai.js +13 -0
  44. package/dist/config/types/analyzer.d.ts +15 -0
  45. package/dist/config/types/analyzer.js +6 -0
  46. package/dist/config/types/data.d.ts +20 -0
  47. package/dist/config/types/data.js +6 -0
  48. package/dist/config/types/fetch.d.ts +9 -0
  49. package/dist/config/types/fetch.js +6 -0
  50. package/dist/config/types/index.d.ts +32 -0
  51. package/dist/config/types/index.js +11 -0
  52. package/dist/config/types/log.d.ts +11 -0
  53. package/dist/config/types/log.js +6 -0
  54. package/dist/core/ai/index.d.ts +11 -0
  55. package/dist/core/ai/index.js +18 -0
  56. package/dist/core/ai/parser/index.d.ts +12 -0
  57. package/dist/core/ai/parser/index.js +44 -0
  58. package/dist/core/ai/parser/validator.d.ts +18 -0
  59. package/dist/core/ai/parser/validator.js +179 -0
  60. package/dist/core/ai/prompt/index.d.ts +20 -0
  61. package/dist/core/ai/prompt/index.js +75 -0
  62. package/dist/core/ai/prompt/system-prompt.md +210 -0
  63. package/dist/core/ai/providers/gemini.d.ts +25 -0
  64. package/dist/core/ai/providers/gemini.js +74 -0
  65. package/dist/core/ai/providers/index.d.ts +6 -0
  66. package/dist/core/ai/providers/index.js +9 -0
  67. package/dist/core/ai/types/index.d.ts +7 -0
  68. package/dist/core/ai/types/index.js +6 -0
  69. package/dist/core/ai/types/options.d.ts +14 -0
  70. package/dist/core/ai/types/options.js +6 -0
  71. package/dist/core/ai/types/provider.d.ts +19 -0
  72. package/dist/core/ai/types/provider.js +6 -0
  73. package/dist/core/ai/types/result.d.ts +64 -0
  74. package/dist/core/ai/types/result.js +6 -0
  75. package/dist/core/ai/utils/api-key.d.ts +15 -0
  76. package/dist/core/ai/utils/api-key.js +36 -0
  77. package/dist/core/ai/utils/index.d.ts +6 -0
  78. package/dist/core/ai/utils/index.js +11 -0
  79. package/dist/core/ai/utils/retry.d.ts +15 -0
  80. package/dist/core/ai/utils/retry.js +37 -0
  81. package/dist/core/analyzer/builder.d.ts +23 -0
  82. package/dist/core/analyzer/builder.js +113 -0
  83. package/dist/core/analyzer/content/chunker.d.ts +18 -0
  84. package/dist/core/analyzer/content/chunker.js +74 -0
  85. package/dist/core/analyzer/content/index.d.ts +7 -0
  86. package/dist/core/analyzer/content/index.js +13 -0
  87. package/dist/core/analyzer/content/transformer.d.ts +19 -0
  88. package/dist/core/analyzer/content/transformer.js +33 -0
  89. package/dist/core/analyzer/index.d.ts +17 -0
  90. package/dist/core/analyzer/index.js +21 -0
  91. package/dist/core/analyzer/periods/detector.d.ts +17 -0
  92. package/dist/core/analyzer/periods/detector.js +36 -0
  93. package/dist/core/analyzer/periods/index.d.ts +6 -0
  94. package/dist/core/analyzer/periods/index.js +11 -0
  95. package/dist/core/analyzer/periods/splitter.d.ts +11 -0
  96. package/dist/core/analyzer/periods/splitter.js +35 -0
  97. package/dist/core/analyzer/stats/index.d.ts +7 -0
  98. package/dist/core/analyzer/stats/index.js +13 -0
  99. package/dist/core/analyzer/stats/reply-stats.d.ts +15 -0
  100. package/dist/core/analyzer/stats/reply-stats.js +45 -0
  101. package/dist/core/analyzer/stats/topic-stats.d.ts +16 -0
  102. package/dist/core/analyzer/stats/topic-stats.js +51 -0
  103. package/dist/core/analyzer/stats/user-overview.d.ts +9 -0
  104. package/dist/core/analyzer/stats/user-overview.js +52 -0
  105. package/dist/core/analyzer/types/index.d.ts +7 -0
  106. package/dist/core/analyzer/types/index.js +6 -0
  107. package/dist/core/analyzer/types/input.d.ts +13 -0
  108. package/dist/core/analyzer/types/input.js +6 -0
  109. package/dist/core/analyzer/types/internal.d.ts +28 -0
  110. package/dist/core/analyzer/types/internal.js +6 -0
  111. package/dist/core/analyzer/types/output.d.ts +68 -0
  112. package/dist/core/analyzer/types/output.js +6 -0
  113. package/dist/core/analyzer/utils/date-parser.d.ts +41 -0
  114. package/dist/core/analyzer/utils/date-parser.js +118 -0
  115. package/dist/core/analyzer/utils/index.d.ts +6 -0
  116. package/dist/core/analyzer/utils/index.js +18 -0
  117. package/dist/core/analyzer/utils/stats.d.ts +12 -0
  118. package/dist/core/analyzer/utils/stats.js +64 -0
  119. package/dist/core/v2ex/index.d.ts +10 -0
  120. package/dist/core/v2ex/index.js +27 -0
  121. package/dist/core/v2ex/parsers/index.d.ts +8 -0
  122. package/dist/core/v2ex/parsers/index.js +15 -0
  123. package/dist/core/v2ex/parsers/replies-page.d.ts +11 -0
  124. package/dist/core/v2ex/parsers/replies-page.js +114 -0
  125. package/dist/core/v2ex/parsers/selectors/index.d.ts +10 -0
  126. package/dist/core/v2ex/parsers/selectors/index.js +18 -0
  127. package/dist/core/v2ex/parsers/selectors/pagination.d.ts +11 -0
  128. package/dist/core/v2ex/parsers/selectors/pagination.js +14 -0
  129. package/dist/core/v2ex/parsers/selectors/replies-page.d.ts +21 -0
  130. package/dist/core/v2ex/parsers/selectors/replies-page.js +24 -0
  131. package/dist/core/v2ex/parsers/selectors/topic-detail.d.ts +19 -0
  132. package/dist/core/v2ex/parsers/selectors/topic-detail.js +22 -0
  133. package/dist/core/v2ex/parsers/selectors/topics-list-page.d.ts +11 -0
  134. package/dist/core/v2ex/parsers/selectors/topics-list-page.js +14 -0
  135. package/dist/core/v2ex/parsers/selectors/user-profile.d.ts +11 -0
  136. package/dist/core/v2ex/parsers/selectors/user-profile.js +14 -0
  137. package/dist/core/v2ex/parsers/topic-detail.d.ts +11 -0
  138. package/dist/core/v2ex/parsers/topic-detail.js +94 -0
  139. package/dist/core/v2ex/parsers/topics-list-page.d.ts +11 -0
  140. package/dist/core/v2ex/parsers/topics-list-page.js +90 -0
  141. package/dist/core/v2ex/parsers/user-profile.d.ts +11 -0
  142. package/dist/core/v2ex/parsers/user-profile.js +70 -0
  143. package/dist/core/v2ex/parsers/utils/index.d.ts +6 -0
  144. package/dist/core/v2ex/parsers/utils/index.js +9 -0
  145. package/dist/core/v2ex/parsers/utils/pagination.d.ts +19 -0
  146. package/dist/core/v2ex/parsers/utils/pagination.js +29 -0
  147. package/dist/core/v2ex/types/entities.d.ts +45 -0
  148. package/dist/core/v2ex/types/entities.js +7 -0
  149. package/dist/core/v2ex/types/index.d.ts +6 -0
  150. package/dist/core/v2ex/types/index.js +6 -0
  151. package/dist/core/v2ex/types/parse-result.d.ts +64 -0
  152. package/dist/core/v2ex/types/parse-result.js +7 -0
  153. package/dist/core/v2ex/urls/constants.d.ts +5 -0
  154. package/dist/core/v2ex/urls/constants.js +8 -0
  155. package/dist/core/v2ex/urls/index.d.ts +7 -0
  156. package/dist/core/v2ex/urls/index.js +16 -0
  157. package/dist/core/v2ex/urls/topic-urls.d.ts +19 -0
  158. package/dist/core/v2ex/urls/topic-urls.js +48 -0
  159. package/dist/core/v2ex/urls/user-urls.d.ts +24 -0
  160. package/dist/core/v2ex/urls/user-urls.js +36 -0
  161. package/dist/core/v2ex/use-cases/index.d.ts +8 -0
  162. package/dist/core/v2ex/use-cases/index.js +14 -0
  163. package/dist/core/v2ex/use-cases/types.d.ts +31 -0
  164. package/dist/core/v2ex/use-cases/types.js +7 -0
  165. package/dist/core/v2ex/use-cases/user/index.d.ts +10 -0
  166. package/dist/core/v2ex/use-cases/user/index.js +16 -0
  167. package/dist/core/v2ex/use-cases/user/profile.d.ts +14 -0
  168. package/dist/core/v2ex/use-cases/user/profile.js +51 -0
  169. package/dist/core/v2ex/use-cases/user/replies.d.ts +14 -0
  170. package/dist/core/v2ex/use-cases/user/replies.js +20 -0
  171. package/dist/core/v2ex/use-cases/user/topic-urls.d.ts +21 -0
  172. package/dist/core/v2ex/use-cases/user/topic-urls.js +29 -0
  173. package/dist/core/v2ex/use-cases/user/topics-detail.d.ts +30 -0
  174. package/dist/core/v2ex/use-cases/user/topics-detail.js +62 -0
  175. package/dist/core/v2ex/use-cases/utils/index.d.ts +6 -0
  176. package/dist/core/v2ex/use-cases/utils/index.js +9 -0
  177. package/dist/core/v2ex/use-cases/utils/page-orchestrator.d.ts +24 -0
  178. package/dist/core/v2ex/use-cases/utils/page-orchestrator.js +93 -0
  179. package/dist/infra/fetcher/agent.d.ts +10 -0
  180. package/dist/infra/fetcher/agent.js +17 -0
  181. package/dist/infra/fetcher/fetcher.d.ts +10 -0
  182. package/dist/infra/fetcher/fetcher.js +81 -0
  183. package/dist/infra/fetcher/index.d.ts +3 -0
  184. package/dist/infra/fetcher/index.js +19 -0
  185. package/dist/infra/fetcher/types.d.ts +29 -0
  186. package/dist/infra/fetcher/types.js +6 -0
  187. package/dist/infra/logger/colors.d.ts +15 -0
  188. package/dist/infra/logger/colors.js +18 -0
  189. package/dist/infra/logger/index.d.ts +16 -0
  190. package/dist/infra/logger/index.js +19 -0
  191. package/dist/infra/logger/logger.d.ts +34 -0
  192. package/dist/infra/logger/logger.js +101 -0
  193. package/dist/infra/storage/cleaner.d.ts +24 -0
  194. package/dist/infra/storage/cleaner.js +73 -0
  195. package/dist/infra/storage/index.d.ts +7 -0
  196. package/dist/infra/storage/index.js +15 -0
  197. package/dist/infra/storage/paths.d.ts +26 -0
  198. package/dist/infra/storage/paths.js +53 -0
  199. package/dist/infra/storage/reader.d.ts +15 -0
  200. package/dist/infra/storage/reader.js +34 -0
  201. package/dist/infra/storage/types.d.ts +21 -0
  202. package/dist/infra/storage/types.js +18 -0
  203. package/dist/infra/storage/writer.d.ts +16 -0
  204. package/dist/infra/storage/writer.js +31 -0
  205. package/package.json +89 -0
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ /**
3
+ * Analyzer 输出构建器
4
+ * 整合所有模块,生成最终的 AI 输入数据
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.buildAnalyzerOutput = buildAnalyzerOutput;
8
+ const periods_1 = require("./periods");
9
+ const user_overview_1 = require("./stats/user-overview");
10
+ const topic_stats_1 = require("./stats/topic-stats");
11
+ const reply_stats_1 = require("./stats/reply-stats");
12
+ const content_1 = require("./content");
13
+ const utils_1 = require("./utils");
14
+ /**
15
+ * 构建完整的 Analyzer 输出
16
+ *
17
+ * 处理流程:
18
+ * 1. 计算用户总览
19
+ * 2. 提取所有活动日期
20
+ * 3. 检测活跃期边界
21
+ * 4. 将数据分割到各活跃期
22
+ * 5. 计算每个活跃期的统计
23
+ * 6. 转换并分片内容
24
+ *
25
+ * @param data - V2EX 抓取的原始用户数据
26
+ * @param referenceDate - 参考日期(用于相对时间解析)
27
+ * @returns 完整的 Analyzer 输出
28
+ */
29
+ function buildAnalyzerOutput(data, referenceDate = new Date()) {
30
+ // 1. 计算用户总览
31
+ const userOverview = (0, user_overview_1.calculateUserOverview)(data, referenceDate);
32
+ // 2. 提取所有活动日期并排序
33
+ const activities = extractActivities(data, referenceDate);
34
+ // 3. 检测活跃期边界
35
+ const boundaries = (0, periods_1.detectPeriodBoundaries)(activities);
36
+ // 4. 将数据分割到各活跃期
37
+ const periods = (0, periods_1.splitByPeriods)(boundaries, data.topics, data.replies, referenceDate);
38
+ // 5. 计算每个活跃期的统计
39
+ const periodStats = periods.map((period) => calculatePeriodStats(period, referenceDate));
40
+ // 6. 构建 PeriodsSummary
41
+ const summary = {
42
+ totalPeriods: periods.length,
43
+ periods: periodStats,
44
+ };
45
+ // 7. 转换并分片内容
46
+ const contents = buildContents(periods);
47
+ return {
48
+ userOverview,
49
+ summary,
50
+ contents,
51
+ };
52
+ }
53
+ /**
54
+ * 从原始数据中提取所有活动日期
55
+ * 用于检测活跃期边界
56
+ */
57
+ function extractActivities(data, referenceDate) {
58
+ const activities = [];
59
+ // 从帖子提取日期(绝对时间)
60
+ for (const topic of data.topics) {
61
+ const parsed = (0, utils_1.parseAbsoluteDate)(topic.createdAt);
62
+ if (parsed) {
63
+ activities.push({ date: parsed.date });
64
+ }
65
+ }
66
+ // 从回复提取日期(相对时间)
67
+ for (const reply of data.replies) {
68
+ const parsed = (0, utils_1.parseRelativeTime)(reply.replyTime, referenceDate);
69
+ if (parsed) {
70
+ activities.push({ date: parsed.date });
71
+ }
72
+ }
73
+ // 按日期升序排序
74
+ activities.sort((a, b) => a.date.getTime() - b.date.getTime());
75
+ return activities;
76
+ }
77
+ /**
78
+ * 计算单个活跃期的统计
79
+ */
80
+ function calculatePeriodStats(period, referenceDate) {
81
+ const topicStats = (0, topic_stats_1.calculateTopicStats)({
82
+ topics: period.topics,
83
+ startDate: period.startDate,
84
+ endDate: period.endDate,
85
+ });
86
+ const replyStats = (0, reply_stats_1.calculateReplyStats)({
87
+ replies: period.replies,
88
+ referenceDate,
89
+ });
90
+ return {
91
+ ...topicStats,
92
+ ...replyStats,
93
+ };
94
+ }
95
+ /**
96
+ * 构建所有活跃期的内容
97
+ */
98
+ function buildContents(periods) {
99
+ const contents = [];
100
+ for (const period of periods) {
101
+ const result = (0, content_1.chunkPeriodContent)(period);
102
+ if (Array.isArray(result)) {
103
+ // 分片结果
104
+ contents.push(...result);
105
+ }
106
+ else {
107
+ // 完整内容
108
+ contents.push(result);
109
+ }
110
+ }
111
+ return contents;
112
+ }
113
+ //# sourceMappingURL=builder.js.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * 内容分片器
3
+ * 将大量内容分割成适合 AI 处理的小块
4
+ */
5
+ import type { ActivePeriod } from '../types';
6
+ import type { PeriodContent, PeriodContentChunk } from '../types';
7
+ /**
8
+ * 将活跃期内容转换并按需分片
9
+ *
10
+ * 分片规则:
11
+ * - topics > CHUNK_MAX_TOPICS 或 replies > CHUNK_MAX_REPLIES → 分片
12
+ * - 同一 chunk 只能包含同一活跃期的内容
13
+ *
14
+ * @param period - 单个活跃期数据
15
+ * @returns 完整内容 PeriodContent 或分片数组 PeriodContentChunk[]
16
+ */
17
+ export declare function chunkPeriodContent(period: ActivePeriod): PeriodContent | PeriodContentChunk[];
18
+ //# sourceMappingURL=chunker.d.ts.map
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ /**
3
+ * 内容分片器
4
+ * 将大量内容分割成适合 AI 处理的小块
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.chunkPeriodContent = chunkPeriodContent;
8
+ const config_1 = require("../../../config");
9
+ const transformer_1 = require("./transformer");
10
+ /**
11
+ * 将活跃期内容转换并按需分片
12
+ *
13
+ * 分片规则:
14
+ * - topics > CHUNK_MAX_TOPICS 或 replies > CHUNK_MAX_REPLIES → 分片
15
+ * - 同一 chunk 只能包含同一活跃期的内容
16
+ *
17
+ * @param period - 单个活跃期数据
18
+ * @returns 完整内容 PeriodContent 或分片数组 PeriodContentChunk[]
19
+ */
20
+ function chunkPeriodContent(period) {
21
+ const analyzerConfig = (0, config_1.getConfig)().analyzer;
22
+ const CHUNK_MAX_TOPICS = analyzerConfig?.chunkMaxTopics ?? 20;
23
+ const CHUNK_MAX_REPLIES = analyzerConfig?.chunkMaxReplies ?? 100;
24
+ const { index: periodIndex, topics, replies } = period;
25
+ // 转换为 AI 格式
26
+ const contentTopics = (0, transformer_1.transformTopics)(topics);
27
+ const contentReplies = (0, transformer_1.transformReplies)(replies);
28
+ // 判断是否需要分片
29
+ const needsChunking = contentTopics.length > CHUNK_MAX_TOPICS || contentReplies.length > CHUNK_MAX_REPLIES;
30
+ if (!needsChunking) {
31
+ // 内容量小,返回完整内容
32
+ return {
33
+ periodIndex,
34
+ topics: contentTopics,
35
+ replies: contentReplies,
36
+ };
37
+ }
38
+ // 需要分片
39
+ return createChunks(periodIndex, contentTopics, contentReplies, CHUNK_MAX_TOPICS, CHUNK_MAX_REPLIES);
40
+ }
41
+ /**
42
+ * 创建内容分片
43
+ *
44
+ * 分片策略:
45
+ * - 帖子按 CHUNK_MAX_TOPICS 分组
46
+ * - 回复按 CHUNK_MAX_REPLIES 分组
47
+ * - 每个 chunk 可同时包含帖子和回复
48
+ */
49
+ function createChunks(periodIndex, topics, replies, CHUNK_MAX_TOPICS, CHUNK_MAX_REPLIES) {
50
+ // 计算需要的 chunk 数量
51
+ const topicChunks = Math.ceil(topics.length / CHUNK_MAX_TOPICS);
52
+ const replyChunks = Math.ceil(replies.length / CHUNK_MAX_REPLIES);
53
+ const totalChunks = Math.max(topicChunks, replyChunks, 1);
54
+ const chunks = [];
55
+ for (let i = 0; i < totalChunks; i++) {
56
+ // 切分帖子
57
+ const topicStart = i * CHUNK_MAX_TOPICS;
58
+ const topicEnd = topicStart + CHUNK_MAX_TOPICS;
59
+ const chunkTopics = topics.slice(topicStart, topicEnd);
60
+ // 切分回复
61
+ const replyStart = i * CHUNK_MAX_REPLIES;
62
+ const replyEnd = replyStart + CHUNK_MAX_REPLIES;
63
+ const chunkReplies = replies.slice(replyStart, replyEnd);
64
+ chunks.push({
65
+ periodIndex,
66
+ chunkIndex: i,
67
+ totalChunksInPeriod: totalChunks,
68
+ topics: chunkTopics,
69
+ replies: chunkReplies,
70
+ });
71
+ }
72
+ return chunks;
73
+ }
74
+ //# sourceMappingURL=chunker.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 内容处理模块
3
+ * 导出转换器和分片器
4
+ */
5
+ export { transformTopics, transformReplies } from './transformer';
6
+ export { chunkPeriodContent } from './chunker';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ /**
3
+ * 内容处理模块
4
+ * 导出转换器和分片器
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.chunkPeriodContent = exports.transformReplies = exports.transformTopics = void 0;
8
+ var transformer_1 = require("./transformer");
9
+ Object.defineProperty(exports, "transformTopics", { enumerable: true, get: function () { return transformer_1.transformTopics; } });
10
+ Object.defineProperty(exports, "transformReplies", { enumerable: true, get: function () { return transformer_1.transformReplies; } });
11
+ var chunker_1 = require("./chunker");
12
+ Object.defineProperty(exports, "chunkPeriodContent", { enumerable: true, get: function () { return chunker_1.chunkPeriodContent; } });
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 内容转换器
3
+ * 将原始 V2EX 数据转换为 AI 可消费的格式
4
+ */
5
+ import type { V2exReply, V2exTopicDetail } from '../../../core/v2ex/types/entities';
6
+ import type { ContentTopic, ContentReply } from '../types';
7
+ /**
8
+ * 将帖子转换为 AI 输入格式
9
+ * @param topics - 单个活跃期内的帖子数组
10
+ * @returns 转换后的 ContentTopic 数组
11
+ */
12
+ export declare function transformTopics(topics: V2exTopicDetail[]): ContentTopic[];
13
+ /**
14
+ * 将回复转换为 AI 输入格式
15
+ * @param replies - 单个活跃期内的回复数组
16
+ * @returns 转换后的 ContentReply 数组
17
+ */
18
+ export declare function transformReplies(replies: V2exReply[]): ContentReply[];
19
+ //# sourceMappingURL=transformer.d.ts.map
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ /**
3
+ * 内容转换器
4
+ * 将原始 V2EX 数据转换为 AI 可消费的格式
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.transformTopics = transformTopics;
8
+ exports.transformReplies = transformReplies;
9
+ /**
10
+ * 将帖子转换为 AI 输入格式
11
+ * @param topics - 单个活跃期内的帖子数组
12
+ * @returns 转换后的 ContentTopic 数组
13
+ */
14
+ function transformTopics(topics) {
15
+ return topics.map((topic) => ({
16
+ title: topic.title,
17
+ nodeName: topic.nodeName,
18
+ content: topic.content,
19
+ }));
20
+ }
21
+ /**
22
+ * 将回复转换为 AI 输入格式
23
+ * @param replies - 单个活跃期内的回复数组
24
+ * @returns 转换后的 ContentReply 数组
25
+ */
26
+ function transformReplies(replies) {
27
+ return replies.map((reply) => ({
28
+ topicTitle: reply.topicTitle,
29
+ nodeName: reply.nodeName,
30
+ content: reply.content,
31
+ }));
32
+ }
33
+ //# sourceMappingURL=transformer.js.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Analyzer 模块公共 API
3
+ *
4
+ * 用法示例:
5
+ * ```typescript
6
+ * import { buildAnalyzerOutput } from '../../core/analyzer';
7
+ *
8
+ * const rawData: RawUserData = { profile, topics, replies, isTopicsHidden };
9
+ * const output = buildAnalyzerOutput(rawData);
10
+ * // output.userOverview - 用户总览
11
+ * // output.summary - 活跃期统计汇总
12
+ * // output.contents - 分片后的内容
13
+ * ```
14
+ */
15
+ export { buildAnalyzerOutput } from './builder';
16
+ export type { RawUserData, AnalyzerOutput, UserOverview, PeriodsSummary, SinglePeriodStats, PeriodContent, PeriodContentChunk, ContentTopic, ContentReply, ActivePeriod, PeriodBoundary, } from './types';
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ /**
3
+ * Analyzer 模块公共 API
4
+ *
5
+ * 用法示例:
6
+ * ```typescript
7
+ * import { buildAnalyzerOutput } from '../../core/analyzer';
8
+ *
9
+ * const rawData: RawUserData = { profile, topics, replies, isTopicsHidden };
10
+ * const output = buildAnalyzerOutput(rawData);
11
+ * // output.userOverview - 用户总览
12
+ * // output.summary - 活跃期统计汇总
13
+ * // output.contents - 分片后的内容
14
+ * ```
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.buildAnalyzerOutput = void 0;
18
+ // 主入口函数
19
+ var builder_1 = require("./builder");
20
+ Object.defineProperty(exports, "buildAnalyzerOutput", { enumerable: true, get: function () { return builder_1.buildAnalyzerOutput; } });
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * 活跃期检测器
3
+ * 通过检测暂停期(>60天无活动)将用户活动分割为多个活跃期
4
+ */
5
+ import type { PeriodBoundary } from '../types';
6
+ interface Activity {
7
+ date: Date;
8
+ }
9
+ /**
10
+ * 检测活跃期边界
11
+ * @param activities 按时间升序排列的活动列表
12
+ * @param thresholdDays 暂停期阈值(天),默认 60
13
+ * @returns 活跃期边界列表
14
+ */
15
+ export declare function detectPeriodBoundaries(activities: Activity[], thresholdDays?: number): PeriodBoundary[];
16
+ export {};
17
+ //# sourceMappingURL=detector.d.ts.map
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ /**
3
+ * 活跃期检测器
4
+ * 通过检测暂停期(>60天无活动)将用户活动分割为多个活跃期
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.detectPeriodBoundaries = detectPeriodBoundaries;
8
+ const config_1 = require("../../../config");
9
+ /**
10
+ * 检测活跃期边界
11
+ * @param activities 按时间升序排列的活动列表
12
+ * @param thresholdDays 暂停期阈值(天),默认 60
13
+ * @returns 活跃期边界列表
14
+ */
15
+ function detectPeriodBoundaries(activities, thresholdDays = (0, config_1.getConfig)().analyzer?.inactivityThreshold ?? 60) {
16
+ if (activities.length === 0)
17
+ return [];
18
+ const thresholdMs = thresholdDays * 24 * 60 * 60 * 1000;
19
+ const periods = [];
20
+ let periodStart = activities[0].date;
21
+ let lastActivity = activities[0].date;
22
+ for (let i = 1; i < activities.length; i++) {
23
+ const current = activities[i].date;
24
+ const gap = current.getTime() - lastActivity.getTime();
25
+ if (gap > thresholdMs) {
26
+ // 间隔超过阈值,结束当前活跃期并开启新的
27
+ periods.push({ startDate: periodStart, endDate: lastActivity });
28
+ periodStart = current;
29
+ }
30
+ lastActivity = current;
31
+ }
32
+ // 结束最后一个活跃期
33
+ periods.push({ startDate: periodStart, endDate: lastActivity });
34
+ return periods;
35
+ }
36
+ //# sourceMappingURL=detector.js.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Periods 入口
3
+ */
4
+ export { detectPeriodBoundaries } from './detector';
5
+ export { splitByPeriods } from './splitter';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ /**
3
+ * Periods 入口
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.splitByPeriods = exports.detectPeriodBoundaries = void 0;
7
+ var detector_1 = require("./detector");
8
+ Object.defineProperty(exports, "detectPeriodBoundaries", { enumerable: true, get: function () { return detector_1.detectPeriodBoundaries; } });
9
+ var splitter_1 = require("./splitter");
10
+ Object.defineProperty(exports, "splitByPeriods", { enumerable: true, get: function () { return splitter_1.splitByPeriods; } });
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 数据分割器
3
+ * 将帖子和回复分配到对应的活跃期
4
+ */
5
+ import type { V2exReply, V2exTopicDetail } from '../../../core/v2ex/types/entities';
6
+ import type { ActivePeriod, PeriodBoundary } from '../types';
7
+ /**
8
+ * 将帖子和回复分配到各活跃期
9
+ */
10
+ export declare function splitByPeriods(boundaries: PeriodBoundary[], topics: V2exTopicDetail[], replies: V2exReply[], referenceDate?: Date): ActivePeriod[];
11
+ //# sourceMappingURL=splitter.d.ts.map
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ /**
3
+ * 数据分割器
4
+ * 将帖子和回复分配到对应的活跃期
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.splitByPeriods = splitByPeriods;
8
+ const utils_1 = require("../utils");
9
+ /**
10
+ * 将帖子和回复分配到各活跃期
11
+ */
12
+ function splitByPeriods(boundaries, topics, replies, referenceDate = new Date()) {
13
+ return boundaries.map((boundary, index) => {
14
+ const periodTopics = topics.filter((topic) => {
15
+ const parsed = (0, utils_1.parseAbsoluteDate)(topic.createdAt);
16
+ if (!parsed)
17
+ return false;
18
+ return parsed.date >= boundary.startDate && parsed.date <= boundary.endDate;
19
+ });
20
+ const periodReplies = replies.filter((reply) => {
21
+ const parsed = (0, utils_1.parseRelativeTime)(reply.replyTime, referenceDate);
22
+ if (!parsed)
23
+ return false;
24
+ return parsed.date >= boundary.startDate && parsed.date <= boundary.endDate;
25
+ });
26
+ return {
27
+ index,
28
+ startDate: boundary.startDate,
29
+ endDate: boundary.endDate,
30
+ topics: periodTopics,
31
+ replies: periodReplies,
32
+ };
33
+ });
34
+ }
35
+ //# sourceMappingURL=splitter.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Stats 入口
3
+ */
4
+ export { calculateUserOverview } from './user-overview';
5
+ export { calculateTopicStats } from './topic-stats';
6
+ export { calculateReplyStats } from './reply-stats';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ /**
3
+ * Stats 入口
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.calculateReplyStats = exports.calculateTopicStats = exports.calculateUserOverview = void 0;
7
+ var user_overview_1 = require("./user-overview");
8
+ Object.defineProperty(exports, "calculateUserOverview", { enumerable: true, get: function () { return user_overview_1.calculateUserOverview; } });
9
+ var topic_stats_1 = require("./topic-stats");
10
+ Object.defineProperty(exports, "calculateTopicStats", { enumerable: true, get: function () { return topic_stats_1.calculateTopicStats; } });
11
+ var reply_stats_1 = require("./reply-stats");
12
+ Object.defineProperty(exports, "calculateReplyStats", { enumerable: true, get: function () { return reply_stats_1.calculateReplyStats; } });
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * 回复统计计算
3
+ */
4
+ import type { V2exReply } from '../../../core/v2ex/types/entities';
5
+ import type { SinglePeriodStats } from '../types';
6
+ interface ReplyStatsInput {
7
+ replies: V2exReply[];
8
+ referenceDate?: Date;
9
+ }
10
+ /**
11
+ * 计算回复相关统计
12
+ */
13
+ export declare function calculateReplyStats(input: ReplyStatsInput): Pick<SinglePeriodStats, 'replyCount' | 'avgReplyLength' | 'directReplyRatio' | 'avgRepliedTopicHeat' | 'replyWeekdayDistribution' | 'replyNodeDistribution'>;
14
+ export {};
15
+ //# sourceMappingURL=reply-stats.d.ts.map
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ /**
3
+ * 回复统计计算
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.calculateReplyStats = calculateReplyStats;
7
+ const config_1 = require("../../../config");
8
+ const utils_1 = require("../utils");
9
+ /**
10
+ * 计算回复相关统计
11
+ */
12
+ function calculateReplyStats(input) {
13
+ const { replies, referenceDate = new Date() } = input;
14
+ if (replies.length === 0) {
15
+ return {
16
+ replyCount: 0,
17
+ avgReplyLength: 0,
18
+ directReplyRatio: 0,
19
+ avgRepliedTopicHeat: 0,
20
+ replyWeekdayDistribution: null,
21
+ replyNodeDistribution: {},
22
+ };
23
+ }
24
+ // 计算平均长度
25
+ const lengths = replies.map((r) => r.content.length);
26
+ // 计算直接回复率
27
+ const directReplies = replies.filter((r) => r.isDirectReply).length;
28
+ // 计算参与话题热度
29
+ const topicHeats = replies.map((r) => r.topicReplyCount);
30
+ // 计算星期分布
31
+ const replyDates = replies
32
+ .map((r) => (0, utils_1.parseRelativeTime)(r.replyTime, referenceDate)?.date)
33
+ .filter((d) => d !== undefined);
34
+ // 只有足够的解析成功才计算星期分布
35
+ const weekdayDist = replyDates.length >= replies.length * 0.5 ? (0, utils_1.weekdayDistribution)(replyDates) : null;
36
+ return {
37
+ replyCount: replies.length,
38
+ avgReplyLength: (0, utils_1.average)(lengths),
39
+ directReplyRatio: directReplies / replies.length,
40
+ avgRepliedTopicHeat: (0, utils_1.average)(topicHeats),
41
+ replyWeekdayDistribution: weekdayDist,
42
+ replyNodeDistribution: (0, utils_1.topN)(replies, (r) => r.nodeName, (0, config_1.getConfig)().analyzer?.nodeDistributionTopN ?? 3),
43
+ };
44
+ }
45
+ //# sourceMappingURL=reply-stats.js.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * 帖子统计计算
3
+ */
4
+ import type { V2exTopicDetail } from '../../../core/v2ex/types/entities';
5
+ import type { SinglePeriodStats } from '../types';
6
+ interface TopicStatsInput {
7
+ topics: V2exTopicDetail[];
8
+ startDate: Date;
9
+ endDate: Date;
10
+ }
11
+ /**
12
+ * 计算帖子相关统计
13
+ */
14
+ export declare function calculateTopicStats(input: TopicStatsInput): Pick<SinglePeriodStats, 'timeRange' | 'topicCount' | 'avgTopicReplyCount' | 'avgTopicClickCount' | 'avgTopicLifecycleDays' | 'topicInteractionRatio' | 'topicHourDistribution' | 'topicNodeDistribution'>;
15
+ export {};
16
+ //# sourceMappingURL=topic-stats.d.ts.map
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ /**
3
+ * 帖子统计计算
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.calculateTopicStats = calculateTopicStats;
7
+ const config_1 = require("../../../config");
8
+ const utils_1 = require("../utils");
9
+ /**
10
+ * 计算帖子相关统计
11
+ */
12
+ function calculateTopicStats(input) {
13
+ const { topics, startDate, endDate } = input;
14
+ const replyCounts = topics.map((t) => t.replyCount);
15
+ const clickCounts = topics.map((t) => t.clickCount);
16
+ // 计算帖子生命周期
17
+ const lifecycleDays = topics
18
+ .map((t) => calculateLifecycleDays(t))
19
+ .filter((d) => d !== null);
20
+ // 计算小时分布
21
+ const topicDates = topics
22
+ .map((t) => (0, utils_1.parseAbsoluteDate)(t.createdAt)?.date)
23
+ .filter((d) => d !== undefined);
24
+ // 计算互动率
25
+ const totalReplies = replyCounts.reduce((a, b) => a + b, 0);
26
+ const totalClicks = clickCounts.reduce((a, b) => a + b, 0);
27
+ return {
28
+ timeRange: (0, utils_1.formatTimeRange)(startDate, endDate),
29
+ topicCount: topics.length,
30
+ avgTopicReplyCount: (0, utils_1.average)(replyCounts),
31
+ avgTopicClickCount: (0, utils_1.average)(clickCounts),
32
+ avgTopicLifecycleDays: (0, utils_1.average)(lifecycleDays),
33
+ topicInteractionRatio: totalClicks > 0 ? totalReplies / totalClicks : 0,
34
+ topicHourDistribution: (0, utils_1.hourDistribution)(topicDates),
35
+ topicNodeDistribution: (0, utils_1.topN)(topics, (t) => t.nodeName, (0, config_1.getConfig)().analyzer?.nodeDistributionTopN ?? 3),
36
+ };
37
+ }
38
+ /**
39
+ * 计算帖子生命周期(天)
40
+ */
41
+ function calculateLifecycleDays(topic) {
42
+ if (!topic.lastReplyTime)
43
+ return null;
44
+ const created = (0, utils_1.parseAbsoluteDate)(topic.createdAt);
45
+ const lastReply = (0, utils_1.parseAbsoluteDate)(topic.lastReplyTime);
46
+ if (!created || !lastReply)
47
+ return null;
48
+ const diffMs = lastReply.date.getTime() - created.date.getTime();
49
+ return diffMs / (1000 * 60 * 60 * 24);
50
+ }
51
+ //# sourceMappingURL=topic-stats.js.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 用户总览计算
3
+ */
4
+ import type { RawUserData, UserOverview } from '../types';
5
+ /**
6
+ * 计算用户总览
7
+ */
8
+ export declare function calculateUserOverview(data: RawUserData, referenceDate?: Date): UserOverview;
9
+ //# sourceMappingURL=user-overview.d.ts.map
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ /**
3
+ * 用户总览计算
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.calculateUserOverview = calculateUserOverview;
7
+ const utils_1 = require("../utils");
8
+ /**
9
+ * 计算用户总览
10
+ */
11
+ function calculateUserOverview(data, referenceDate = new Date()) {
12
+ const { profile, topics, replies, isTopicsHidden } = data;
13
+ // 计算最后活动时间
14
+ const lastActiveTime = getLastActiveTime(topics, replies, referenceDate);
15
+ // 计算发帖/回复比率,无回复时为 null
16
+ const topicReplyRatio = replies.length > 0 ? topics.length / replies.length : null;
17
+ return {
18
+ joinDate: profile.joinDate,
19
+ lastActiveTime,
20
+ topicReplyRatio,
21
+ totalTopics: isTopicsHidden ? null : topics.length,
22
+ totalReplies: replies.length,
23
+ isTopicsHidden,
24
+ dailyRanking: profile.dailyRanking,
25
+ };
26
+ }
27
+ /**
28
+ * 获取最后活动时间
29
+ */
30
+ function getLastActiveTime(topics, replies, referenceDate) {
31
+ let lastDate = null;
32
+ // 检查帖子的最后时间
33
+ for (const topic of topics) {
34
+ const parsed = (0, utils_1.parseAbsoluteDate)(topic.createdAt);
35
+ if (parsed && (!lastDate || parsed.date > lastDate)) {
36
+ lastDate = parsed.date;
37
+ }
38
+ }
39
+ // 检查回复的最后时间
40
+ for (const reply of replies) {
41
+ const parsed = (0, utils_1.parseRelativeTime)(reply.replyTime, referenceDate);
42
+ if (parsed && (!lastDate || parsed.date > lastDate)) {
43
+ lastDate = parsed.date;
44
+ }
45
+ }
46
+ if (!lastDate) {
47
+ return 'unknown';
48
+ }
49
+ // 格式化为 ISO 字符串
50
+ return lastDate.toISOString();
51
+ }
52
+ //# sourceMappingURL=user-overview.js.map