aiden-shared-calculations-unified 1.0.86 → 1.0.87

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 (70) hide show
  1. package/calculations/core/asset-pnl-status.js +36 -106
  2. package/calculations/core/asset-position-size.js +40 -91
  3. package/calculations/core/average-daily-pnl-all-users.js +18 -57
  4. package/calculations/core/average-daily-pnl-per-sector.js +41 -88
  5. package/calculations/core/average-daily-pnl-per-stock.js +38 -91
  6. package/calculations/core/average-daily-position-pnl.js +19 -49
  7. package/calculations/core/holding-duration-per-asset.js +25 -127
  8. package/calculations/core/instrument-price-change-1d.js +30 -49
  9. package/calculations/core/instrument-price-momentum-20d.js +50 -60
  10. package/calculations/core/long-position-per-stock.js +39 -68
  11. package/calculations/core/overall-holding-duration.js +16 -87
  12. package/calculations/core/overall-profitability-ratio.js +11 -40
  13. package/calculations/core/platform-buy-sell-sentiment.js +41 -124
  14. package/calculations/core/platform-daily-bought-vs-sold-count.js +41 -99
  15. package/calculations/core/platform-daily-ownership-delta.js +68 -126
  16. package/calculations/core/platform-ownership-per-sector.js +45 -96
  17. package/calculations/core/platform-total-positions-held.js +20 -80
  18. package/calculations/core/pnl-distribution-per-stock.js +29 -135
  19. package/calculations/core/price-metrics.js +95 -206
  20. package/calculations/core/profitability-ratio-per-sector.js +34 -79
  21. package/calculations/core/profitability-ratio-per-stock.js +32 -88
  22. package/calculations/core/profitability-skew-per-stock.js +41 -94
  23. package/calculations/core/profitable-and-unprofitable-status.js +44 -76
  24. package/calculations/core/sentiment-per-stock.js +24 -77
  25. package/calculations/core/short-position-per-stock.js +35 -43
  26. package/calculations/core/social-activity-aggregation.js +26 -49
  27. package/calculations/core/social-asset-posts-trend.js +38 -94
  28. package/calculations/core/social-event-correlation.js +26 -93
  29. package/calculations/core/social-sentiment-aggregation.js +20 -44
  30. package/calculations/core/social-top-mentioned-words.js +35 -87
  31. package/calculations/core/social-topic-interest-evolution.js +22 -111
  32. package/calculations/core/social-topic-sentiment-matrix.js +38 -104
  33. package/calculations/core/social-word-mentions-trend.js +27 -104
  34. package/calculations/core/speculator-asset-sentiment.js +31 -72
  35. package/calculations/core/speculator-danger-zone.js +48 -84
  36. package/calculations/core/speculator-distance-to-stop-loss-per-leverage.js +20 -52
  37. package/calculations/core/speculator-distance-to-tp-per-leverage.js +23 -53
  38. package/calculations/core/speculator-entry-distance-to-sl-per-leverage.js +20 -50
  39. package/calculations/core/speculator-entry-distance-to-tp-per-leverage.js +23 -50
  40. package/calculations/core/speculator-leverage-per-asset.js +25 -64
  41. package/calculations/core/speculator-leverage-per-sector.js +27 -63
  42. package/calculations/core/speculator-risk-reward-ratio-per-asset.js +24 -53
  43. package/calculations/core/speculator-stop-loss-distance-by-sector-short-long-breakdown.js +55 -68
  44. package/calculations/core/speculator-stop-loss-distance-by-ticker-short-long-breakdown.js +54 -71
  45. package/calculations/core/speculator-stop-loss-per-asset.js +19 -44
  46. package/calculations/core/speculator-take-profit-per-asset.js +20 -57
  47. package/calculations/core/speculator-tsl-per-asset.js +17 -56
  48. package/calculations/core/total-long-figures.js +16 -31
  49. package/calculations/core/total-long-per-sector.js +39 -61
  50. package/calculations/core/total-short-figures.js +13 -32
  51. package/calculations/core/total-short-per-sector.js +39 -61
  52. package/calculations/core/users-processed.js +11 -46
  53. package/calculations/gauss/cohort-capital-flow.js +54 -173
  54. package/calculations/gauss/cohort-definer.js +77 -163
  55. package/calculations/gauss/daily-dna-filter.js +29 -83
  56. package/calculations/gauss/gauss-divergence-signal.js +22 -109
  57. package/calculations/gem/cohort-momentum-state.js +27 -72
  58. package/calculations/gem/cohort-skill-definition.js +36 -52
  59. package/calculations/gem/platform-conviction-divergence.js +18 -60
  60. package/calculations/gem/quant-skill-alpha-signal.js +25 -98
  61. package/calculations/gem/skilled-cohort-flow.js +67 -175
  62. package/calculations/gem/skilled-unskilled-divergence.js +18 -73
  63. package/calculations/gem/unskilled-cohort-flow.js +64 -172
  64. package/calculations/helix/helix-contrarian-signal.js +20 -114
  65. package/calculations/helix/herd-consensus-score.js +42 -124
  66. package/calculations/helix/winner-loser-flow.js +36 -118
  67. package/calculations/pyro/risk-appetite-index.js +33 -74
  68. package/calculations/pyro/squeeze-potential.js +30 -87
  69. package/calculations/pyro/volatility-signal.js +33 -78
  70. package/package.json +1 -1
@@ -1,115 +1,63 @@
1
1
  /**
2
- * @fileoverview Calculation (Pass 2 - Meta) for social top words.
3
- * --- FIX ---
4
- * 1. Changed logic to read from 'socialDoc' directly, not 'socialDoc.posts'.
5
- * 2. Changed tokenizer to read from 'post.textSnippet' to match schema.md.
2
+ * @fileoverview Calculation (Pass 2 - Meta) for top words.
3
+ * REFACTORED: Counts word frequency in social posts.
6
4
  */
7
- const STOPWORDS = new Set([
8
- 'a', 'an', 'the', 'is', 'are', 'was', 'were', 'be', 'being', 'been',
9
- 'it', 'its', 'it\'s', 'i', 'me', 'my', 'mine', 'you', 'your', 'yours',
10
- 'he', 'him', 'his', 'she', 'her', 'hers', 'they', 'them', 'their', 'theirs',
11
- 'we', 'us', 'our', 'ours', 'what', 'which', 'who', 'whom', 'this', 'that',
12
- 'these', 'those', 'am', 'and', 'but', 'if', 'or', 'because', 'as', 'until',
13
- 'while', 'of', 'at', 'by', 'for', 'with', 'about', 'against', 'between',
14
- 'into', 'through', 'during', 'before', 'after', 'above', 'below', 'to',
15
- 'from', 'up', 'down', 'in', 'out', 'on', 'off', 'over', 'under', 'again',
16
- 'further', 'then', 'once', 'here', 'there', 'when', 'where', 'why', 'how',
17
- 'all', 'any', 'both', 'each', 'few', 'more', 'most', 'other', 'some',
18
- 'such', 'no', 'nor', 'not', 'only', 'own', 'same', 'so', 'than', 'too',
19
- 'very', 's', 't', 'can', 'will', 'just', 'don', 'should', 'now',
20
- 'stock', 'stocks', 'market', 'price', 'trade', 'trading', 'buy', 'sell',
21
- 'long', 'short', 'chart', 'week', 'day', 'today', 'going', 'think'
22
- ]);
23
-
24
5
  class SocialTopMentionedWords {
25
-
26
- constructor() {
27
- this.result = {};
28
- }
6
+ constructor() { this.result = {}; }
29
7
 
30
8
  static getMetadata() {
31
9
  return {
32
10
  type: 'meta',
33
11
  rootDataDependencies: ['social'],
34
12
  isHistorical: false,
35
- userType: 'n'/'a',
13
+ userType: 'n/a',
36
14
  category: 'core_social'
37
15
  };
38
16
  }
39
17
 
40
- static getDependencies() {
41
- return [];
42
- }
18
+ static getDependencies() { return []; }
43
19
 
44
20
  static getSchema() {
45
- const wordSchema = {
46
- "type": "object",
47
- "properties": { "count": { "type": "number" } }
48
- };
49
-
50
21
  return {
51
22
  "type": "object",
52
- "description": "Calculates the top 50 most mentioned words in social posts.",
53
- "patternProperties": { "^.*$": wordSchema },
54
- "additionalProperties": wordSchema
23
+ "description": "Top 50 mentioned words/tickers.",
24
+ "patternProperties": { "^.*$": { "type": "number" } }
55
25
  };
56
26
  }
57
27
 
58
- _tokenize(text) {
59
- if (!text) return [];
60
- return text.toLowerCase()
61
- .replace(/[^\w\s$]/g, '')
62
- .split(/\s+/)
63
- .filter(word =>
64
- word.length > 2 &&
65
- !STOPWORDS.has(word) &&
66
- !/^\d+$/.test(word)
67
- );
68
- }
69
-
70
- async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
28
+ process(context) {
29
+ const socialPosts = context.social?.today || {};
71
30
  const wordCounts = new Map();
72
-
73
- // 'dateStr' (Arg 1) is the metaPayload from the worker
74
- const socialDoc = dateStr.social; // This IS the map of posts
75
-
76
- // --- THIS IS THE FIX (Part 1) ---
77
- // 'socialDoc' is the map of posts, not an object containing .posts
78
- if (!socialDoc || typeof socialDoc !== 'object') {
79
- // --- END FIX (Part 1) ---
80
- this.result = {};
81
- return;
82
- }
83
-
84
- for (const post of Object.values(socialDoc)) {
85
- // --- THIS IS THE FIX (Part 2) ---
86
- // schema.md provides 'textSnippet', not 'fullText'
87
- const words = this._tokenize(post.textSnippet);
88
- // --- END FIX (Part 2) ---
89
- for (const word of words) {
90
- wordCounts.set(word, (wordCounts.get(word) || 0) + 1);
31
+ const STOP_WORDS = new Set(['the', 'and', 'this', 'that', 'for', 'is', 'are', 'was', 'with', 'on', 'in', 'to', 'of', 'it', 'my', 'me', 'at']);
32
+
33
+ for (const postId in socialPosts) {
34
+ const post = socialPosts[postId];
35
+ const text = (post.textSnippet || "").toLowerCase();
36
+
37
+ // Basic Tokenization
38
+ const tokens = text.split(/[\s.,!?;:()]+/);
39
+
40
+ for (const token of tokens) {
41
+ if (token.length > 2 && !STOP_WORDS.has(token) && !/^\d+$/.test(token)) {
42
+ wordCounts.set(token, (wordCounts.get(token) || 0) + 1);
43
+ }
44
+ }
45
+
46
+ // Also count tagged tickers explicitly if available
47
+ if (post.tickers && Array.isArray(post.tickers)) {
48
+ for (const ticker of post.tickers) {
49
+ const t = `$${ticker.toLowerCase()}`;
50
+ wordCounts.set(t, (wordCounts.get(t) || 0) + 1);
51
+ }
91
52
  }
92
53
  }
93
54
 
94
- const sortedWords = Array.from(wordCounts.entries())
95
- .sort((a, b) => b[1] - a[1])
96
- .slice(0, 50);
97
-
98
- const result = {};
99
- for (const [word, count] of sortedWords) {
100
- result[word] = { count: count };
101
- }
102
-
103
- this.result = result;
104
- }
105
-
106
- async getResult(fetchedDependencies) {
107
- return this.result;
55
+ // Sort and take top 50
56
+ const sorted = [...wordCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 50);
57
+ this.result = Object.fromEntries(sorted);
108
58
  }
109
59
 
110
- reset() {
111
- this.result = {};
112
- }
60
+ async getResult() { return this.result; }
61
+ reset() { this.result = {}; }
113
62
  }
114
-
115
63
  module.exports = SocialTopMentionedWords;
@@ -1,141 +1,52 @@
1
1
  /**
2
- * @fileoverview Calculation (Pass 2 - Meta) for social topic evolution.
3
- * --- FIX ---
4
- * 1. Changed 'process' signature to 5-arg 'meta' standard.
5
- * 2. Changed data access to read 'dateStr.social' (Arg 1)
6
- * 3. Changed 'socialDoc.posts' (Object) to 'socialDoc' (Object).
7
- * 4. Changed 'post.timestamp' to 'post.createdAt'.
8
- * 5. Changed 'post.topics' to 'post.sentiment.topics'.
9
- * 6. Changed helper to read 'post.sentiment.overallSentiment'.
2
+ * @fileoverview Calculation (Pass 2 - Meta) for topic frequency.
3
+ * REFACTORED: Counts topics (same as events, but conceptual separation).
10
4
  */
11
5
  class SocialTopicInterestEvolution {
12
-
13
- constructor() {
14
- this.result = {};
15
- }
6
+ constructor() { this.result = {}; }
16
7
 
17
8
  static getMetadata() {
18
9
  return {
19
10
  type: 'meta',
20
11
  rootDataDependencies: ['social'],
21
- isHistorical: true,
22
- userType: 'n'/'a',
12
+ isHistorical: false, // Only today's snapshot
13
+ userType: 'n/a',
23
14
  category: 'core_social'
24
15
  };
25
16
  }
26
17
 
27
- static getDependencies() {
28
- return [];
29
- }
18
+ static getDependencies() { return []; }
30
19
 
31
20
  static getSchema() {
32
21
  const topicSchema = {
33
22
  "type": "object",
34
- "properties": {
35
- "posts_1d_pct": { "type": "number" },
36
- "posts_7d_pct": { "type": "number" },
37
- "sentiment_1d": { "type": "number" },
38
- "sentiment_7d": { "type": "number" }
39
- }
40
- };
41
-
42
- return {
43
- "type": "object",
44
- "description": "Calculates the evolution of post volume and sentiment for all social topics.",
45
- "patternProperties": { "^.*$": topicSchema },
46
- "additionalProperties": topicSchema
23
+ "properties": { "mention_count": { "type": "number" } }
47
24
  };
25
+ return { "type": "object", "patternProperties": { "^.*$": topicSchema } };
48
26
  }
49
27
 
50
- _getSentimentScore(sentimentString) {
51
- const sentiment = sentimentString?.toLowerCase();
52
- if (sentiment === 'bullish') return 1;
53
- if (sentiment === 'bearish') return -1;
54
- return 0;
55
- }
56
-
57
- // --- THIS IS THE FIX ---
58
- async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
59
-
60
- // Get social data from Arg 1 (metaPayload)
61
- const socialDoc = dateStr.social; // This IS the map of posts
62
- // Get the date from Arg 1
63
- const todayDateStr = dateStr.date;
64
-
65
- if (!socialDoc || typeof socialDoc !== 'object' || !todayDateStr) {
66
- this.result = {};
67
- return;
68
- }
69
-
70
- const today = this._parseDate(todayDateStr);
71
- const sevenDaysAgo = this._parseDate(todayDateStr);
72
- sevenDaysAgo.setUTCDate(today.getUTCDate() - 7);
73
-
74
- const topicStats = new Map();
75
- let total1d = 0;
76
- let total7d = 0;
77
-
78
- const _init = (topic) => {
79
- if (!topicStats.has(topic)) {
80
- topicStats.set(topic, { c1: 0, c7: 0, s1: 0, s7: 0 });
81
- }
82
- };
83
-
84
- for (const post of Object.values(socialDoc)) {
85
- const postDate = this._parseDate(post.createdAt);
86
- const is1d = postDate.toISOString().slice(0, 10) === todayDateStr;
87
- const is7d = postDate >= sevenDaysAgo;
88
-
89
- if (!is7d) continue;
28
+ process(context) {
29
+ const socialPosts = context.social?.today || {};
30
+ const topicCounts = new Map();
90
31
 
32
+ for (const postId in socialPosts) {
33
+ const post = socialPosts[postId];
91
34
  const topics = post.sentiment?.topics || [];
92
- if (topics.length === 0) continue;
93
-
94
- const sentiment = this._getSentimentScore(post.sentiment?.overallSentiment);
95
-
35
+
96
36
  for (const topic of topics) {
97
- _init(topic);
98
- const stats = topicStats.get(topic);
99
-
100
- if (is7d) {
101
- stats.c7++;
102
- stats.s7 += sentiment;
103
- total7d++;
104
- }
105
- if (is1d) {
106
- stats.c1++;
107
- stats.s1 += sentiment;
108
- total1d++;
109
- }
37
+ const t = topic.toUpperCase();
38
+ topicCounts.set(t, (topicCounts.get(t) || 0) + 1);
110
39
  }
111
40
  }
112
41
 
113
- const result = {};
114
- for (const [topic, stats] of topicStats.entries()) {
115
- result[topic] = {
116
- posts_1d_pct: (total1d > 0) ? (stats.c1 / total1d) * 100 : 0,
117
- posts_7d_pct: (total7d > 0) ? (stats.c7 / total7d) * 100 : 0,
118
- sentiment_1d: (stats.c1 > 0) ? (stats.s1 / stats.c1) : 0,
119
- sentiment_7d: (stats.c7 > 0) ? (stats.s7 / stats.c7) : 0,
120
- };
42
+ const output = {};
43
+ for (const [topic, count] of topicCounts.entries()) {
44
+ output[topic] = { mention_count: count };
121
45
  }
122
-
123
- this.result = result;
124
- }
125
- // --- END FIX ---
126
-
127
- _parseDate(dateStr) {
128
- // Helper to ensure UTC parsing
129
- return new Date(String(dateStr).slice(0, 10) + 'T00:00:00Z');
46
+ this.result = output;
130
47
  }
131
48
 
132
- async getResult(fetchedDependencies) {
133
- return this.result;
134
- }
135
-
136
- reset() {
137
- this.result = {};
138
- }
49
+ async getResult() { return this.result; }
50
+ reset() { this.result = {}; }
139
51
  }
140
-
141
52
  module.exports = SocialTopicInterestEvolution;
@@ -1,137 +1,71 @@
1
1
  /**
2
- * @fileoverview Calculation (Pass 2 - Meta) for social topic/sentiment.
3
- * --- FIX ---
4
- * 1. Changed 'process' signature to 5-arg 'meta' standard.
5
- * 2. Removed 'calculationUtils.loadInstrumentMappings()' as it's
6
- * not available in the test harness 'meta' signature.
7
- * 3. Logic now uses 'config.instrumentToTicker' (Arg 4)
8
- * which IS provided by the worker.
9
- * 4. Changed 'socialDoc.posts' to 'socialDoc' (Arg 1).
10
- * 5. Changed 'post.sentiment.label' to 'post.sentiment.overallSentiment'.
11
- * 6. Changed 'post.topics' to 'post.sentiment.topics'.
12
- * 7. Changed 'post.instrumentIds' to 'post.tickers'.
2
+ * @fileoverview Calculation (Pass 2 - Meta) for Topic vs Sentiment.
3
+ * REFACTORED: Groups sentiment scores by topic.
13
4
  */
14
-
15
5
  class SocialTopicSentimentMatrix {
16
-
17
- constructor() {
18
- this.result = {};
19
- }
6
+ constructor() { this.result = {}; }
20
7
 
21
8
  static getMetadata() {
22
9
  return {
23
10
  type: 'meta',
24
11
  rootDataDependencies: ['social'],
25
12
  isHistorical: false,
26
- userType: 'n'/'a',
13
+ userType: 'n/a',
27
14
  category: 'core_social'
28
15
  };
29
16
  }
30
17
 
31
- static getDependencies() {
32
- return [];
33
- }
18
+ static getDependencies() { return []; }
34
19
 
35
20
  static getSchema() {
36
- const schema = {
21
+ const topicSchema = {
37
22
  "type": "object",
38
23
  "properties": {
39
- "avg_sentiment": { "type": "number" },
40
- "post_count": { "type": "number" }
24
+ "bullish_count": { "type": "number" },
25
+ "bearish_count": { "type": "number" },
26
+ "neutral_count": { "type": "number" },
27
+ "net_sentiment": { "type": "number" }
41
28
  }
42
29
  };
43
-
44
- return {
45
- "type": "object",
46
- "description": "Calculates the average sentiment and post count for all topics and all instruments.",
47
- "properties": {
48
- "by_topic": { "type": "object", "patternProperties": { "^.*$": schema }, "additionalProperties": schema },
49
- "by_instrument": { "type": "object", "patternProperties": { "^.*$": schema }, "additionalProperties": schema }
50
- },
51
- "required": ["by_topic", "by_instrument"]
52
- };
53
- }
54
-
55
- _getSentimentScore(sentimentString) {
56
- const sentiment = sentimentString?.toLowerCase();
57
- if (sentiment === 'bullish') return 1;
58
- if (sentiment === 'bearish') return -1;
59
- return 0;
60
- }
61
-
62
- _init(map, key) {
63
- if (!map.has(key)) {
64
- map.set(key, { score_sum: 0, count: 0 });
65
- }
30
+ return { "type": "object", "patternProperties": { "^.*$": topicSchema } };
66
31
  }
67
32
 
68
- // --- THIS IS THE FIX ---
69
- async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
70
- // Get mappings from Arg 4 (config)
71
- const { instrumentToTicker } = config;
72
-
73
- // Get social data from Arg 1 (dateStr is metaPayload)
74
- const socialDoc = dateStr.social;
75
-
76
- if (!socialDoc || typeof socialDoc !== 'object' || !instrumentToTicker) {
77
- this.result = { by_topic: {}, by_instrument: {} };
78
- return;
79
- }
80
-
81
- const topicMap = new Map();
82
- const instrumentMap = new Map();
33
+ process(context) {
34
+ const socialPosts = context.social?.today || {};
35
+ const topicStats = new Map();
83
36
 
84
- for (const post of Object.values(socialDoc)) {
85
- const sentiment = this._getSentimentScore(post.sentiment?.overallSentiment);
86
-
87
- // 1. Process topics
37
+ for (const postId in socialPosts) {
38
+ const post = socialPosts[postId];
88
39
  const topics = post.sentiment?.topics || [];
89
- for (const topic of topics) {
90
- this._init(topicMap, topic);
91
- const data = topicMap.get(topic);
92
- data.score_sum += sentiment;
93
- data.count++;
94
- }
95
-
96
- // 2. Process instruments (tickers)
97
- const tickers = post.tickers || [];
98
- for (const ticker of tickers) {
99
- if (!ticker) continue;
40
+ const sentiment = (post.sentiment?.overallSentiment || 'neutral').toLowerCase();
100
41
 
101
- this._init(instrumentMap, ticker);
102
- const data = instrumentMap.get(ticker);
103
- data.score_sum += sentiment;
104
- data.count++;
42
+ for (const topic of topics) {
43
+ const t = topic.toUpperCase();
44
+ if (!topicStats.has(t)) {
45
+ topicStats.set(t, { bullish: 0, bearish: 0, neutral: 0 });
46
+ }
47
+ const stats = topicStats.get(t);
48
+
49
+ if (sentiment === 'bullish') stats.bullish++;
50
+ else if (sentiment === 'bearish') stats.bearish++;
51
+ else stats.neutral++;
105
52
  }
106
53
  }
107
54
 
108
- const result = { by_topic: {}, by_instrument: {} };
109
-
110
- for (const [topic, data] of topicMap.entries()) {
111
- result.by_topic[topic] = {
112
- avg_sentiment: (data.count > 0) ? (data.score_sum / data.count) : 0,
113
- post_count: data.count
114
- };
115
- }
116
-
117
- for (const [ticker, data] of instrumentMap.entries()) {
118
- result.by_instrument[ticker] = {
119
- avg_sentiment: (data.count > 0) ? (data.score_sum / data.count) : 0,
120
- post_count: data.count
55
+ const output = {};
56
+ for (const [topic, stats] of topicStats.entries()) {
57
+ const total = stats.bullish + stats.bearish + stats.neutral;
58
+ output[topic] = {
59
+ bullish_count: stats.bullish,
60
+ bearish_count: stats.bearish,
61
+ neutral_count: stats.neutral,
62
+ net_sentiment: total > 0 ? (stats.bullish - stats.bearish) / total : 0
121
63
  };
122
64
  }
123
-
124
- this.result = result;
65
+ this.result = output;
125
66
  }
126
- // --- END FIX ---
127
67
 
128
- async getResult(fetchedDependencies) {
129
- return this.result;
130
- }
131
-
132
- reset() {
133
- this.result = {};
134
- }
68
+ async getResult() { return this.result; }
69
+ reset() { this.result = {}; }
135
70
  }
136
-
137
71
  module.exports = SocialTopicSentimentMatrix;
@@ -1,132 +1,55 @@
1
1
  /**
2
- * @fileoverview Calculation (Pass 2 - Meta) for social word trend.
3
- * --- FIX ---
4
- * 1. Changed 'process' signature to 5-arg 'meta' standard.
5
- * 2. Changed data access to read 'dateStr.social' (Arg 1)
6
- * 3. Changed 'socialDoc.posts' (Array) to 'socialDoc' (Object).
7
- * 4. Changed 'post.timestamp' to 'post.createdAt'.
8
- * 5. Changed 'post.content' to 'post.textSnippet'.
2
+ * @fileoverview Calculation (Pass 2 - Meta) for word frequency trend.
3
+ * REFACTORED: Calculates word counts (identical logic to top words, but for trending analysis).
9
4
  */
10
- const STOPWORDS = new Set([
11
- 'a', 'an', 'the', 'is', 'are', 'was', 'were', 'be', 'being', 'been',
12
- 'it', 'its', 'it\'s', 'i', 'me', 'my', 'mine', 'you', 'your', 'yours',
13
- 'he', 'him', 'his', 'she', 'her', 'hers', 'they', 'them', 'their', 'theirs',
14
- 'we', 'us', 'our', 'ours', 'what', 'which', 'who', 'whom', 'this', 'that',
15
- 'these', 'those', 'am', 'and', 'but', 'if', 'or', 'because', 'as', 'until',
16
- 'while', 'of', 'at', 'by', 'for', 'with', 'about', 'against', 'between',
17
- 'into', 'through', 'during', 'before', 'after', 'above', 'below', 'to',
18
- 'from', 'up', 'down', 'in', 'out', 'on', 'off', 'over', 'under', 'again',
19
- 'further', 'then', 'once', 'here', 'there', 'when', 'where', 'why', 'how',
20
- 'all', 'any', 'both', 'each', 'few', 'more', 'most', 'other', 'some',
21
- 'such', 'no', 'nor', 'not', 'only', 'own', 'same', 'so', 'than', 'too',
22
- 'very', 's', 't', 'can', 'will', 'just', 'don', 'should', 'now',
23
- 'stock', 'stocks', 'market', 'price', 'trade', 'trading', 'buy', 'sell',
24
- 'long', 'short', 'chart', 'week', 'day', 'today', 'going', 'think'
25
- ]);
26
-
27
5
  class SocialWordMentionsTrend {
28
-
29
- constructor() {
30
- this.result = {};
31
- }
6
+ constructor() { this.result = {}; }
32
7
 
33
8
  static getMetadata() {
34
9
  return {
35
10
  type: 'meta',
36
11
  rootDataDependencies: ['social'],
37
- isHistorical: true, // Needs 7 days of posts
38
- userType: 'n'/'a',
12
+ isHistorical: false,
13
+ userType: 'n/a',
39
14
  category: 'core_social'
40
15
  };
41
16
  }
42
17
 
43
- static getDependencies() {
44
- return ['social-top-mentioned-words'];
45
- }
18
+ static getDependencies() { return []; }
46
19
 
47
20
  static getSchema() {
48
- const wordSchema = {
49
- "type": "object",
50
- "properties": {
51
- "count_1d": { "type": "number" },
52
- "count_7d": { "type": "number" }
53
- }
54
- };
55
-
56
21
  return {
57
22
  "type": "object",
58
- "description": "Tracks the 1-day vs 7-day mention count for the top 50 words.",
59
- "patternProperties": { "^.*$": wordSchema },
60
- "additionalProperties": wordSchema
23
+ "patternProperties": { "^.*$": { "type": "number" } }
61
24
  };
62
25
  }
63
26
 
64
- _tokenize(text) {
65
- if (!text) return [];
66
- return text.toLowerCase()
67
- .replace(/[^\w\s$]/g, '')
68
- .split(/\s+/)
69
- .filter(word =>
70
- word.length > 2 &&
71
- !STOPWORDS.has(word) &&
72
- !/^\d+$/.test(word)
73
- );
74
- }
75
-
76
- // --- THIS IS THE FIX ---
77
- async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
78
- // dateStr is Arg 1 (metaPayload), rootData is Arg 2, dependencies is Arg 3
79
-
80
- // Get social data from Arg 1 (worker hack)
81
- const socialDoc = dateStr.social; // This IS the map of posts
82
- // Get dependency from Arg 5
83
- const topWordsData = fetchedDependencies['social-top-mentioned-words'];
84
-
85
- if (!socialDoc || typeof socialDoc !== 'object' || !topWordsData) {
86
- this.result = {};
87
- return;
88
- }
89
-
90
- const topWords = new Set(Object.keys(topWordsData));
91
- // dateStr is the metaPayload, but it has a 'date' field
92
- const today = new Date(dateStr.date + 'T00:00:00Z');
93
- const sevenDaysAgo = new Date(today);
94
- sevenDaysAgo.setUTCDate(today.getUTCDate() - 7);
95
-
96
- const counts = new Map();
97
- for (const word of topWords) {
98
- counts.set(word, { count_1d: 0, count_7d: 0 });
99
- }
100
-
101
- for (const post of Object.values(socialDoc)) {
102
- const postDate = new Date(post.createdAt); // Use 'createdAt'
103
- const is1d = postDate.toISOString().slice(0, 10) === dateStr.date;
104
- const is7d = postDate >= sevenDaysAgo;
105
-
106
- if (!is7d) continue;
27
+ process(context) {
28
+ const socialPosts = context.social?.today || {};
29
+ const wordCounts = new Map();
30
+ const STOP_WORDS = new Set(['the', 'and', 'for', 'that', 'this', 'with', 'you', 'are', 'not']);
107
31
 
108
- const words = this._tokenize(post.textSnippet); // Use 'textSnippet'
32
+ for (const postId in socialPosts) {
33
+ const post = socialPosts[postId];
34
+ const text = (post.textSnippet || "").toLowerCase();
35
+ const tokens = text.split(/[\s.,!?;:()]+/);
109
36
 
110
- for (const word of words) {
111
- if (counts.has(word)) {
112
- const stats = counts.get(word);
113
- if (is7d) stats.count_7d++;
114
- if (is1d) stats.count_1d++;
37
+ for (const token of tokens) {
38
+ if (token.length > 3 && !STOP_WORDS.has(token) && !/^\d+$/.test(token)) {
39
+ wordCounts.set(token, (wordCounts.get(token) || 0) + 1);
115
40
  }
116
41
  }
117
42
  }
118
-
119
- this.result = Object.fromEntries(counts);
120
- }
121
- // --- END FIX ---
122
-
123
- async getResult(fetchedDependencies) {
124
- return this.result;
43
+
44
+ // Return counts for all words found > 2 times
45
+ const filtered = {};
46
+ for (const [word, count] of wordCounts.entries()) {
47
+ if (count > 2) filtered[word] = count;
48
+ }
49
+ this.result = filtered;
125
50
  }
126
51
 
127
- reset() {
128
- this.result = {};
129
- }
52
+ async getResult() { return this.result; }
53
+ reset() { this.result = {}; }
130
54
  }
131
-
132
55
  module.exports = SocialWordMentionsTrend;