@gotza02/sequential-thinking 10000.0.8 → 10000.1.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.
@@ -0,0 +1,380 @@
1
+ /**
2
+ * ALERT MANAGER
3
+ * Real-time alert system with rule engine and notifications
4
+ */
5
+ import { EventEmitter } from 'events';
6
+ import { logger } from '../../../utils.js';
7
+ import { ALERT_CONFIG } from './constants.js';
8
+ export class AlertManager extends EventEmitter {
9
+ rules = new Map();
10
+ checkInterval;
11
+ alertHistory = [];
12
+ maxHistorySize = 1000;
13
+ constructor() {
14
+ super();
15
+ this.setMaxListeners(100);
16
+ }
17
+ /**
18
+ * Start the alert manager
19
+ */
20
+ start() {
21
+ if (this.checkInterval)
22
+ return;
23
+ this.checkInterval = setInterval(() => {
24
+ this.checkScheduledAlerts();
25
+ }, ALERT_CONFIG.CHECK_INTERVAL);
26
+ logger.info('[AlertManager] Started');
27
+ }
28
+ /**
29
+ * Stop the alert manager
30
+ */
31
+ stop() {
32
+ if (this.checkInterval) {
33
+ clearInterval(this.checkInterval);
34
+ this.checkInterval = undefined;
35
+ }
36
+ logger.info('[AlertManager] Stopped');
37
+ }
38
+ /**
39
+ * Add a new alert rule
40
+ */
41
+ addRule(rule) {
42
+ const newRule = {
43
+ ...rule,
44
+ id: `alert-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
45
+ createdAt: Date.now(),
46
+ triggerCount: 0,
47
+ };
48
+ this.rules.set(newRule.id, newRule);
49
+ logger.info(`[AlertManager] Added rule: ${newRule.id} (${newRule.name})`);
50
+ this.emit('rule_added', newRule);
51
+ return newRule;
52
+ }
53
+ /**
54
+ * Remove an alert rule
55
+ */
56
+ removeRule(ruleId) {
57
+ const removed = this.rules.delete(ruleId);
58
+ if (removed) {
59
+ logger.info(`[AlertManager] Removed rule: ${ruleId}`);
60
+ this.emit('rule_removed', ruleId);
61
+ }
62
+ return removed;
63
+ }
64
+ /**
65
+ * Get all rules
66
+ */
67
+ getRules() {
68
+ return Array.from(this.rules.values());
69
+ }
70
+ /**
71
+ * Get a specific rule
72
+ */
73
+ getRule(ruleId) {
74
+ return this.rules.get(ruleId);
75
+ }
76
+ /**
77
+ * Enable/disable a rule
78
+ */
79
+ toggleRule(ruleId, enabled) {
80
+ const rule = this.rules.get(ruleId);
81
+ if (rule) {
82
+ rule.enabled = enabled;
83
+ logger.info(`[AlertManager] Rule ${ruleId} ${enabled ? 'enabled' : 'disabled'}`);
84
+ return true;
85
+ }
86
+ return false;
87
+ }
88
+ /**
89
+ * Process a live event and check for alerts
90
+ */
91
+ processEvent(event) {
92
+ for (const rule of this.rules.values()) {
93
+ if (!rule.enabled)
94
+ continue;
95
+ // Check cooldown
96
+ if (rule.lastTriggered && Date.now() - rule.lastTriggered < rule.cooldown) {
97
+ continue;
98
+ }
99
+ // Check rate limit
100
+ if (rule.triggerCount && rule.triggerCount >= ALERT_CONFIG.MAX_ALERTS_PER_HOUR) {
101
+ continue;
102
+ }
103
+ if (this.evaluateCondition(rule.condition, event)) {
104
+ this.triggerAlert(rule, event);
105
+ }
106
+ }
107
+ }
108
+ /**
109
+ * Evaluate an alert condition against an event
110
+ */
111
+ evaluateCondition(condition, event) {
112
+ switch (condition.type) {
113
+ case 'odds_drop':
114
+ return this.evaluateOddsDrop(condition, event);
115
+ case 'odds_value':
116
+ return this.evaluateOddsValue(condition, event);
117
+ case 'event':
118
+ return this.evaluateEventCondition(condition, event);
119
+ case 'composite':
120
+ return this.evaluateCompositeCondition(condition, event);
121
+ default:
122
+ return false;
123
+ }
124
+ }
125
+ evaluateOddsDrop(condition, event) {
126
+ if (event.type !== 'odds_change')
127
+ return false;
128
+ const oddsEvent = event;
129
+ const { newOdds, change } = oddsEvent.data;
130
+ // Check match filter
131
+ if (condition.matchId && event.matchId !== condition.matchId)
132
+ return false;
133
+ // Check threshold
134
+ if (newOdds > condition.threshold)
135
+ return false;
136
+ // Check percentage drop
137
+ return Math.abs(change) >= condition.percentage;
138
+ }
139
+ evaluateOddsValue(condition, event) {
140
+ if (event.type !== 'odds_change')
141
+ return false;
142
+ const oddsEvent = event;
143
+ const { newOdds } = oddsEvent.data;
144
+ // Check match filter
145
+ if (condition.matchId && event.matchId !== condition.matchId)
146
+ return false;
147
+ // Check max odds
148
+ if (condition.maxOdds && newOdds > condition.maxOdds)
149
+ return false;
150
+ // Calculate value (would need probability data)
151
+ // For now, simplified check
152
+ return true;
153
+ }
154
+ evaluateEventCondition(condition, event) {
155
+ // Check match filter
156
+ if (condition.matchId && event.matchId !== condition.matchId)
157
+ return false;
158
+ // Check event type
159
+ return condition.eventTypes.includes(event.type);
160
+ }
161
+ evaluateCompositeCondition(condition, event) {
162
+ const results = condition.conditions.map(c => this.evaluateCondition(c, event));
163
+ return condition.operator === 'AND'
164
+ ? results.every(r => r)
165
+ : results.some(r => r);
166
+ }
167
+ /**
168
+ * Trigger an alert
169
+ */
170
+ async triggerAlert(rule, event) {
171
+ rule.lastTriggered = Date.now();
172
+ rule.triggerCount = (rule.triggerCount || 0) + 1;
173
+ const message = this.createAlertMessage(rule, event);
174
+ // Store in history
175
+ this.alertHistory.push(message);
176
+ if (this.alertHistory.length > this.maxHistorySize) {
177
+ this.alertHistory.shift();
178
+ }
179
+ // Send notifications
180
+ for (const channel of rule.channels) {
181
+ try {
182
+ await this.sendNotification(channel, message);
183
+ }
184
+ catch (error) {
185
+ logger.error(`[AlertManager] Failed to send notification: ${error}`);
186
+ }
187
+ }
188
+ // Emit event
189
+ this.emit('alert', message);
190
+ logger.info(`[AlertManager] Alert triggered: ${rule.name} (${rule.id})`);
191
+ }
192
+ /**
193
+ * Create an alert message
194
+ */
195
+ createAlertMessage(rule, event) {
196
+ const severity = this.determineSeverity(rule.type, event);
197
+ return {
198
+ ruleId: rule.id,
199
+ ruleName: rule.name,
200
+ type: rule.type,
201
+ severity,
202
+ title: this.formatAlertTitle(rule, event),
203
+ body: this.formatAlertBody(rule, event),
204
+ data: {
205
+ event,
206
+ rule,
207
+ },
208
+ timestamp: Date.now(),
209
+ };
210
+ }
211
+ determineSeverity(type, event) {
212
+ switch (type) {
213
+ case 'goal':
214
+ case 'red_card':
215
+ return 'critical';
216
+ case 'odds_drop':
217
+ case 'odds_value':
218
+ return 'warning';
219
+ default:
220
+ return 'info';
221
+ }
222
+ }
223
+ formatAlertTitle(rule, event) {
224
+ switch (rule.type) {
225
+ case 'odds_drop':
226
+ return `🚨 Odds Drop Alert: ${event.matchId}`;
227
+ case 'odds_value':
228
+ return `💰 Value Bet Alert: ${event.matchId}`;
229
+ case 'goal':
230
+ return `⚽ GOAL! ${event.matchId}`;
231
+ case 'red_card':
232
+ return `🔴 RED CARD! ${event.matchId}`;
233
+ default:
234
+ return `📢 ${rule.name}`;
235
+ }
236
+ }
237
+ formatAlertBody(rule, event) {
238
+ const lines = [];
239
+ lines.push(`Rule: ${rule.name}`);
240
+ lines.push(`Type: ${rule.type}`);
241
+ lines.push(`Match: ${event.matchId}`);
242
+ lines.push(`Time: ${new Date(event.timestamp).toLocaleString()}`);
243
+ if (event.minute) {
244
+ lines.push(`Minute: ${event.minute}'`);
245
+ }
246
+ if (event.data) {
247
+ lines.push('');
248
+ lines.push('Details:');
249
+ lines.push(JSON.stringify(event.data, null, 2));
250
+ }
251
+ return lines.join('\n');
252
+ }
253
+ /**
254
+ * Send notification to a channel
255
+ */
256
+ async sendNotification(channel, message) {
257
+ switch (channel.type) {
258
+ case 'webhook':
259
+ await this.sendWebhook(channel.config.url, message);
260
+ break;
261
+ case 'slack':
262
+ await this.sendSlack(channel.config.webhook, message);
263
+ break;
264
+ case 'discord':
265
+ await this.sendDiscord(channel.config.webhook, message);
266
+ break;
267
+ case 'email':
268
+ await this.sendEmail(channel.config, message);
269
+ break;
270
+ case 'console':
271
+ console.log(`\n${'='.repeat(50)}`);
272
+ console.log(`ALERT: ${message.title}`);
273
+ console.log(`${'='.repeat(50)}`);
274
+ console.log(message.body);
275
+ console.log(`${'='.repeat(50)}\n`);
276
+ break;
277
+ }
278
+ }
279
+ async sendWebhook(url, message) {
280
+ const response = await fetch(url, {
281
+ method: 'POST',
282
+ headers: { 'Content-Type': 'application/json' },
283
+ body: JSON.stringify(message),
284
+ });
285
+ if (!response.ok) {
286
+ throw new Error(`Webhook failed: ${response.status}`);
287
+ }
288
+ }
289
+ async sendSlack(webhook, message) {
290
+ const emoji = message.severity === 'critical' ? '🚨' :
291
+ message.severity === 'warning' ? '⚠️' : 'ℹ️';
292
+ const payload = {
293
+ text: `${emoji} *${message.title}*`,
294
+ attachments: [{
295
+ color: message.severity === 'critical' ? 'danger' :
296
+ message.severity === 'warning' ? 'warning' : 'good',
297
+ text: message.body,
298
+ footer: 'Football Alert System',
299
+ ts: Math.floor(message.timestamp / 1000),
300
+ }],
301
+ };
302
+ const response = await fetch(webhook, {
303
+ method: 'POST',
304
+ headers: { 'Content-Type': 'application/json' },
305
+ body: JSON.stringify(payload),
306
+ });
307
+ if (!response.ok) {
308
+ throw new Error(`Slack webhook failed: ${response.status}`);
309
+ }
310
+ }
311
+ async sendDiscord(webhook, message) {
312
+ const color = message.severity === 'critical' ? 0xff0000 :
313
+ message.severity === 'warning' ? 0xffa500 : 0x00ff00;
314
+ const payload = {
315
+ embeds: [{
316
+ title: message.title,
317
+ description: message.body,
318
+ color: color,
319
+ timestamp: new Date(message.timestamp).toISOString(),
320
+ }],
321
+ };
322
+ const response = await fetch(webhook, {
323
+ method: 'POST',
324
+ headers: { 'Content-Type': 'application/json' },
325
+ body: JSON.stringify(payload),
326
+ });
327
+ if (!response.ok) {
328
+ throw new Error(`Discord webhook failed: ${response.status}`);
329
+ }
330
+ }
331
+ async sendEmail(config, message) {
332
+ // Would integrate with email service like SendGrid, AWS SES, etc.
333
+ logger.info(`[AlertManager] Would send email to ${config.to}: ${message.title}`);
334
+ }
335
+ /**
336
+ * Check scheduled alerts (for time-based alerts)
337
+ */
338
+ checkScheduledAlerts() {
339
+ // Implement time-based alert checking
340
+ // e.g., check for upcoming matches, etc.
341
+ }
342
+ /**
343
+ * Get alert history
344
+ */
345
+ getAlertHistory(limit) {
346
+ const history = [...this.alertHistory].reverse();
347
+ return limit ? history.slice(0, limit) : history;
348
+ }
349
+ /**
350
+ * Clear alert history
351
+ */
352
+ clearHistory() {
353
+ this.alertHistory = [];
354
+ }
355
+ /**
356
+ * Get statistics
357
+ */
358
+ getStats() {
359
+ const rules = Array.from(this.rules.values());
360
+ return {
361
+ totalRules: rules.length,
362
+ enabledRules: rules.filter(r => r.enabled).length,
363
+ totalAlerts: this.alertHistory.length,
364
+ };
365
+ }
366
+ }
367
+ // Singleton instance
368
+ let globalAlertManager = null;
369
+ export function getAlertManager() {
370
+ if (!globalAlertManager) {
371
+ globalAlertManager = new AlertManager();
372
+ }
373
+ return globalAlertManager;
374
+ }
375
+ export function resetAlertManager() {
376
+ if (globalAlertManager) {
377
+ globalAlertManager.stop();
378
+ }
379
+ globalAlertManager = null;
380
+ }
@@ -1,18 +1,19 @@
1
1
  /**
2
- * SPORTS MODULE CACHE SERVICE
3
- * TTL-based caching with file persistence for the football intelligence system
2
+ * ENHANCED CACHE SERVICE
3
+ * TTL-based caching with stale-while-revalidate pattern
4
4
  */
5
5
  /**
6
- * CacheService - Thread-safe TTL-based caching with persistence
6
+ * CacheService - Thread-safe TTL-based caching with stale-while-revalidate
7
7
  */
8
8
  export declare class CacheService {
9
9
  private cache;
10
10
  private cachePath;
11
11
  private maxAge;
12
+ private staleAge;
12
13
  private maxSize;
13
14
  private savePending;
14
15
  private saveTimer;
15
- constructor(cachePath?: string, maxAge?: number, maxSize?: number);
16
+ constructor(cachePath?: string, maxAge?: number, maxSize?: number, staleAge?: number);
16
17
  /**
17
18
  * Generate a cache key from components
18
19
  */
@@ -24,7 +25,16 @@ export declare class CacheService {
24
25
  /**
25
26
  * Set a value in cache
26
27
  */
27
- set<T>(key: string, data: T, ttl?: number): void;
28
+ set<T>(key: string, data: T, ttl?: number, staleTtl?: number): void;
29
+ /**
30
+ * Get or set with stale-while-revalidate pattern
31
+ * Returns cached value immediately, refreshes in background if stale
32
+ */
33
+ getOrSetWithStaleRevalidate<T>(key: string, factory: () => T | Promise<T>, ttl?: number, staleTtl?: number): Promise<T>;
34
+ /**
35
+ * Refresh data in background
36
+ */
37
+ private refreshInBackground;
28
38
  /**
29
39
  * Check if a key exists and is not expired
30
40
  */
@@ -49,6 +59,7 @@ export declare class CacheService {
49
59
  hits: number;
50
60
  age: number;
51
61
  ttl: number;
62
+ isStale: boolean;
52
63
  }>;
53
64
  };
54
65
  /**
@@ -56,7 +67,7 @@ export declare class CacheService {
56
67
  */
57
68
  cleanup(): number;
58
69
  /**
59
- * Evict the oldest entry (LRU)
70
+ * Evict the oldest entry (LRU with hit weighting)
60
71
  */
61
72
  private evictOldest;
62
73
  /**
@@ -78,7 +89,7 @@ export declare class CacheService {
78
89
  /**
79
90
  * Get or set pattern - returns cached value or computes and caches it
80
91
  */
81
- getOrSet<T>(key: string, factory: () => T | Promise<T>, ttl?: number): Promise<T>;
92
+ getOrSet<T>(key: string, factory: () => T | Promise<T>, ttl?: number, staleTtl?: number): Promise<T>;
82
93
  /**
83
94
  * Get multiple keys at once
84
95
  */
@@ -86,7 +97,7 @@ export declare class CacheService {
86
97
  /**
87
98
  * Set multiple keys at once
88
99
  */
89
- setMultiple<T>(entries: Map<string, T>, ttl?: number): void;
100
+ setMultiple<T>(entries: Map<string, T>, ttl?: number, staleTtl?: number): void;
90
101
  /**
91
102
  * Invalidate cache by pattern
92
103
  */