@semanticintent/semantic-chirp-intelligence-mcp 3.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.
@@ -0,0 +1,599 @@
1
+ /**
2
+ * 🏒 Weekend Stream Analysis - Desperation vs Genuine Opportunity
3
+ *
4
+ * Distinguishes weekend streaming desperation (bye-week fillers, injury covers, <1-week value)
5
+ * from genuine opportunities (sustainable roles, >2-week upside, PP time, line jumps).
6
+ *
7
+ * Binary Classification Decision Tree:
8
+ * - Desperation: Context-driven (bye/injury), low floor, no role lock
9
+ * - Genuine: Independent catalysts, high ceiling, sustainable hold
10
+ * - Monitor: 50/50 territory, hot hand but risky matchup
11
+ *
12
+ * Upside Score Formula (0-100):
13
+ * Score = (0.30 * Proj FP/G * 10) + (0.30 * Opp TOI) + (0.20 * Schedule Ease) - (0.20 * Risk %)
14
+ * - >60 = Genuine opportunity
15
+ * - <40 = Desperation stream
16
+ *
17
+ * Semantic Identity: Weekend Stream Classifier
18
+ * Intent: I distinguish desperation streams from genuine opportunities
19
+ * Chirp Style: desperate_or_legit
20
+ */
21
+ import { AnalysisTemplate } from '../template/AnalysisTemplate.js';
22
+ import { ChirpIntelligence } from '../services/ChirpIntelligence.js';
23
+ export class WeekendStreamAnalysis extends AnalysisTemplate {
24
+ yahooClient;
25
+ leagueId;
26
+ teamId;
27
+ constructor(yahooClient, leagueId, teamId) {
28
+ super('analyze_weekend_streams', 'streaming_recommendations');
29
+ this.yahooClient = yahooClient;
30
+ this.leagueId = leagueId;
31
+ this.teamId = teamId;
32
+ }
33
+ /**
34
+ * Semantic Identity for this analysis
35
+ */
36
+ getSemanticIdentity() {
37
+ return `
38
+ 🏒 Weekend Stream Classifier - ICE Analysis
39
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
40
+
41
+ SEMANTIC IDENTITY:
42
+ Tool: analyze_weekend_streams
43
+ Intent: I distinguish desperation streams from genuine opportunities
44
+ Classification: Binary decision tree (Desperation | Genuine | Monitor)
45
+ Time Horizon: Weekend tactical (3-7 day value)
46
+
47
+ INTELLIGENCE LAYERS:
48
+ 1. Schedule Analysis - Weekend games, back-to-backs, opponents
49
+ 2. Context Synthesis - Team byes, injuries, roster gaps
50
+ 3. Metrics Evaluation - Recent PPG, projected FP/G, TOI trends
51
+ 4. Risk Assessment - Injury %, role volatility, matchup difficulty
52
+ 5. Binary Classification - Decision tree logic
53
+ 6. Fit Optimization - Match to roster gaps, suggest drops
54
+ 7. Confidence Scoring - Upside score (0-100)
55
+
56
+ UPSIDE FORMULA:
57
+ Score = (0.30 × Proj FP/G × 10) + (0.30 × Opp TOI) +
58
+ (0.20 × Schedule Ease) - (0.20 × Risk %)
59
+
60
+ Thresholds:
61
+ >60 = Genuine opportunity (sustainable, >2 week hold)
62
+ 40-60 = Monitor territory (hot hand, risky matchup)
63
+ <40 = Desperation stream (bye/injury fill, <1 week)
64
+
65
+ CHIRP STYLE: desperate_or_legit
66
+ "That's not a stream, that's a cry for help" 🆘
67
+ "PP1 lock? Now we're talking genuine upside" 🔥
68
+ `;
69
+ }
70
+ /**
71
+ * Layer 1: Fetch raw data
72
+ * - Free agents (position filtered, ownership capped)
73
+ * - User's roster (check byes/injuries)
74
+ * - Weekend schedule (date filtered)
75
+ * - Trending players
76
+ */
77
+ async fetchData(args) {
78
+ const ownershipMax = args.ownership_max || 50;
79
+ const positions = args.position_filter || ['C', 'LW', 'RW', 'D', 'G'];
80
+ // Fetch free agents across requested positions
81
+ const freeAgentPromises = positions.map(pos => this.yahooClient.searchPlayers(pos, 50, this.leagueId));
82
+ const [freeAgentResults, roster, trendingAdds] = await Promise.all([
83
+ Promise.all(freeAgentPromises),
84
+ this.yahooClient.getTeamRoster(this.leagueId, this.teamId),
85
+ this.yahooClient.getTrendingPlayers('add', 25, this.leagueId)
86
+ ]);
87
+ // Combine and deduplicate free agents
88
+ const allFreeAgents = [];
89
+ for (const result of freeAgentResults) {
90
+ if (result.players) {
91
+ allFreeAgents.push(...result.players);
92
+ }
93
+ }
94
+ const uniqueFreeAgents = Array.from(new Map(allFreeAgents.map(p => [p.player_id, p])).values()).filter(p => (p.percent_owned || 0) <= ownershipMax);
95
+ return {
96
+ freeAgents: uniqueFreeAgents,
97
+ roster,
98
+ trendingAdds: trendingAdds.players || [],
99
+ dateRange: args.date_range
100
+ };
101
+ }
102
+ /**
103
+ * Layer 2: Prepare data for analysis
104
+ * - Identify roster gaps (byes, injuries)
105
+ * - Fetch weekend schedules
106
+ * - Structure for analysis
107
+ */
108
+ async prepareData(rawData, args) {
109
+ // Identify roster gaps
110
+ const rosterGaps = this.identifyRosterGaps(rawData.roster, args);
111
+ // Get weekend schedules for each free agent
112
+ // (In real implementation, would fetch NHL schedule API)
113
+ const weekendSchedules = this.mockWeekendSchedules(rawData.freeAgents, rawData.dateRange);
114
+ // Return with custom properties attached
115
+ const result = {
116
+ availablePlayers: rawData.freeAgents,
117
+ roster: rawData.roster,
118
+ trendingPlayers: rawData.trendingAdds,
119
+ rosterGaps: rosterGaps,
120
+ weekendSchedules: weekendSchedules
121
+ };
122
+ return result;
123
+ }
124
+ /**
125
+ * Layer 2: Identify roster gaps from user's team
126
+ */
127
+ identifyRosterGaps(roster, args) {
128
+ const byeTeams = [];
129
+ const injuredPlayers = [];
130
+ const positionNeeds = [];
131
+ // Check roster for injuries
132
+ if (roster.players) {
133
+ for (const player of roster.players) {
134
+ if (player.status && player.status !== '' && player.status !== 'Healthy') {
135
+ injuredPlayers.push(`${player.name} (${player.team})`);
136
+ }
137
+ }
138
+ }
139
+ // Parse team_needs if provided
140
+ if (args.team_needs) {
141
+ for (const need of args.team_needs) {
142
+ if (need === 'bye_fill') {
143
+ // User indicated bye week gaps
144
+ byeTeams.push('User indicated bye week');
145
+ }
146
+ else if (need === 'injury_cover') {
147
+ // User indicated injury coverage needed
148
+ }
149
+ else if (need.includes('_')) {
150
+ // Position needs like "G_volume", "C_depth"
151
+ const pos = need.split('_')[0];
152
+ if (!positionNeeds.includes(pos)) {
153
+ positionNeeds.push(pos);
154
+ }
155
+ }
156
+ }
157
+ }
158
+ return {
159
+ bye_teams: byeTeams,
160
+ injured_players: injuredPlayers,
161
+ position_needs: positionNeeds,
162
+ gaps_count: byeTeams.length + injuredPlayers.length
163
+ };
164
+ }
165
+ /**
166
+ * Layer 3: Mock weekend schedules
167
+ * (In production, integrate with NHL Schedule API)
168
+ */
169
+ mockWeekendSchedules(players, dateRange) {
170
+ const schedules = new Map();
171
+ for (const player of players) {
172
+ // Mock 1-3 weekend games
173
+ const gameCount = Math.floor(Math.random() * 3) + 1;
174
+ const games = [];
175
+ for (let i = 0; i < gameCount; i++) {
176
+ games.push({
177
+ date: dateRange.start,
178
+ opponent: this.getRandomOpponent(player.team),
179
+ home: Math.random() > 0.5
180
+ });
181
+ }
182
+ schedules.set(player.player_id, {
183
+ player_id: player.player_id,
184
+ games,
185
+ game_count: gameCount,
186
+ has_back_to_back: gameCount >= 2 && Math.random() > 0.7
187
+ });
188
+ }
189
+ return schedules;
190
+ }
191
+ /**
192
+ * Layer 4: Execute weekend stream analysis
193
+ */
194
+ async analyzeData(data, args) {
195
+ const freeAgents = data.availablePlayers || [];
196
+ const dataAny = data;
197
+ const rosterGaps = dataAny.rosterGaps;
198
+ const schedules = dataAny.weekendSchedules;
199
+ // Score and classify each free agent
200
+ const scoredStreams = await Promise.all(freeAgents.map(player => this.analyzeStreamCandidate(player, rosterGaps, schedules, data)));
201
+ // Filter and sort by upside score
202
+ const minScore = args.min_upside_score || 0;
203
+ const sorted = scoredStreams
204
+ .filter(s => s.upside_score >= minScore)
205
+ .sort((a, b) => b.upside_score - a.upside_score);
206
+ // Separate by classification
207
+ const genuine = sorted.filter(s => s.classification === 'genuine');
208
+ const desperation = sorted.filter(s => s.classification === 'desperation');
209
+ const monitor = sorted.filter(s => s.classification === 'monitor');
210
+ // Top picks per classification
211
+ const maxResults = args.max_results || 10;
212
+ const topGenuine = genuine.slice(0, maxResults);
213
+ const topDesperation = desperation.slice(0, maxResults);
214
+ const topMonitor = monitor.slice(0, Math.floor(maxResults / 2));
215
+ return {
216
+ top_genuine: topGenuine,
217
+ top_desperation: topDesperation,
218
+ top_monitor: topMonitor,
219
+ all_streams: sorted.slice(0, 50),
220
+ classification_breakdown: {
221
+ genuine_count: genuine.length,
222
+ desperation_count: desperation.length,
223
+ monitor_count: monitor.length
224
+ },
225
+ roster_gaps: rosterGaps,
226
+ weekend_analysis: this.summarizeWeekendTrends(sorted, schedules)
227
+ };
228
+ }
229
+ /**
230
+ * Layer 5-7: Analyze single stream candidate through all intelligence layers
231
+ */
232
+ async analyzeStreamCandidate(player, gaps, schedules, data) {
233
+ // Get weekend schedule
234
+ const schedule = schedules.get(player.player_id) || {
235
+ player_id: player.player_id,
236
+ games: [],
237
+ game_count: 0,
238
+ has_back_to_back: false
239
+ };
240
+ // Layer 3: Metrics Evaluation
241
+ const metrics = await this.evaluateMetrics(player, data);
242
+ // Layer 1: Schedule Analysis
243
+ const scheduleAnalysis = this.analyzeScheduleEase(schedule, player);
244
+ // Layer 4: Risk Assessment
245
+ const risk = this.assessRisk(player, schedule, metrics);
246
+ // Layer 7: Upside Score Calculation
247
+ const upsideScore = this.calculateUpsideScore(metrics.projected_fpg, metrics.opportunity_toi, scheduleAnalysis.ease_score, risk.total_risk);
248
+ // Layer 2: Context Synthesis (is this driven by user's gaps?)
249
+ const contextDriven = this.isContextDriven(player, gaps);
250
+ // Layer 5: Binary Classification
251
+ const classification = this.classifyStream(upsideScore, contextDriven, metrics, risk.total_risk);
252
+ // Layer 6: Fit Optimization
253
+ const fitAnalysis = this.analyzeFit(player, gaps, classification);
254
+ return {
255
+ ...player,
256
+ classification,
257
+ upside_score: Math.round(upsideScore),
258
+ weekend_games: schedule.game_count,
259
+ schedule_ease: scheduleAnalysis.ease_score,
260
+ context_driven: contextDriven,
261
+ hold_duration: this.determineHoldDuration(classification, metrics),
262
+ catalyst: this.identifyCatalyst(player, metrics, schedule),
263
+ recent_ppg: metrics.recent_ppg,
264
+ projected_fpg: metrics.projected_fpg,
265
+ opportunity_toi: metrics.opportunity_toi,
266
+ risk_percentage: risk.total_risk,
267
+ matchup_quality: scheduleAnalysis.matchup_quality,
268
+ back_to_back: schedule.has_back_to_back,
269
+ drop_suggestion: fitAnalysis.drop_suggestion,
270
+ fit_reason: fitAnalysis.fit_reason
271
+ };
272
+ }
273
+ /**
274
+ * Layer 7: Calculate upside score (0-100)
275
+ * Formula: (0.30 × Proj FP/G × 10) + (0.30 × Opp TOI) + (0.20 × Schedule Ease) - (0.20 × Risk %)
276
+ */
277
+ calculateUpsideScore(projectedFpg, oppToi, scheduleEase, risk) {
278
+ const score = 0.30 * (projectedFpg * 10) +
279
+ 0.30 * oppToi +
280
+ 0.20 * scheduleEase -
281
+ 0.20 * risk;
282
+ return Math.max(0, Math.min(100, score));
283
+ }
284
+ /**
285
+ * Layer 5: Binary Classification Decision Tree
286
+ */
287
+ classifyStream(upsideScore, contextDriven, metrics, risk) {
288
+ // Desperation: Context-driven + Low floor + High risk
289
+ if (contextDriven && upsideScore < 40) {
290
+ return 'desperation';
291
+ }
292
+ // Desperation: No role lock + Low score
293
+ if (metrics.opportunity_toi < 12 && upsideScore < 45) {
294
+ return 'desperation';
295
+ }
296
+ // Genuine: High upside + Independent catalysts + Low risk
297
+ if (upsideScore >= 60 && !contextDriven && risk < 40) {
298
+ return 'genuine';
299
+ }
300
+ // Genuine: Strong role lock + Good projection
301
+ if (metrics.opportunity_toi >= 16 && upsideScore >= 55) {
302
+ return 'genuine';
303
+ }
304
+ // Monitor: Everything in between (50/50 territory)
305
+ return 'monitor';
306
+ }
307
+ /**
308
+ * Layer 3: Evaluate player metrics
309
+ */
310
+ async evaluateMetrics(player, data) {
311
+ // Mock metrics - in production, fetch real stats
312
+ const isTrending = data.trendingPlayers?.some((t) => t.player_id === player.player_id);
313
+ const basePerformance = Math.random() * 0.8 + 0.2; // 0.2 - 1.0 PPG
314
+ const recent_ppg = basePerformance;
315
+ const projected_fpg = isTrending ? basePerformance * 1.15 : basePerformance;
316
+ // TOI estimate (higher for better players)
317
+ const ownership = player.percent_owned || 0;
318
+ const opportunity_toi = 10 + (ownership / 100) * 10 + Math.random() * 5; // 10-25 min
319
+ return {
320
+ recent_ppg,
321
+ projected_fpg,
322
+ opportunity_toi,
323
+ pp_role: opportunity_toi > 18 ? 'PP1' : opportunity_toi > 15 ? 'PP2' : 'None',
324
+ line_position: opportunity_toi > 16 ? 'Top-6' : 'Bottom-6'
325
+ };
326
+ }
327
+ /**
328
+ * Layer 1: Analyze schedule ease
329
+ */
330
+ analyzeScheduleEase(schedule, player) {
331
+ if (schedule.game_count === 0) {
332
+ return { ease_score: 0, matchup_quality: 'average' };
333
+ }
334
+ // Base score on game count
335
+ let easeScore = schedule.game_count * 30;
336
+ // Bonus for multiple games
337
+ if (schedule.game_count >= 3)
338
+ easeScore += 15;
339
+ // Penalty for back-to-back
340
+ if (schedule.has_back_to_back)
341
+ easeScore -= 10;
342
+ // Opponent strength (mock - in production use team rankings)
343
+ const opponentStrength = Math.random() * 100;
344
+ if (opponentStrength < 30) {
345
+ easeScore += 20; // Weak opponent
346
+ }
347
+ else if (opponentStrength > 70) {
348
+ easeScore -= 15; // Strong opponent
349
+ }
350
+ const matchup_quality = opponentStrength < 30 ? 'elite' :
351
+ opponentStrength < 50 ? 'favorable' :
352
+ opponentStrength < 70 ? 'average' : 'difficult';
353
+ return {
354
+ ease_score: Math.max(0, Math.min(100, easeScore)),
355
+ matchup_quality
356
+ };
357
+ }
358
+ /**
359
+ * Layer 4: Assess risk
360
+ */
361
+ assessRisk(player, schedule, metrics) {
362
+ let totalRisk = 20; // Base risk
363
+ // Injury status
364
+ if (player.status && player.status !== '' && player.status !== 'Healthy') {
365
+ totalRisk += 30;
366
+ }
367
+ // Low opportunity = higher risk
368
+ if (metrics.opportunity_toi < 12) {
369
+ totalRisk += 25;
370
+ }
371
+ // Back-to-back fatigue
372
+ if (schedule.has_back_to_back) {
373
+ totalRisk += 15;
374
+ }
375
+ // Low ownership = unproven = risk
376
+ const ownership = player.percent_owned || 0;
377
+ if (ownership < 10)
378
+ totalRisk += 15;
379
+ if (ownership > 30)
380
+ totalRisk -= 10; // Higher ownership = proven
381
+ return {
382
+ total_risk: Math.max(0, Math.min(100, totalRisk)),
383
+ factors: {
384
+ injury: player.status ? true : false,
385
+ low_toi: metrics.opportunity_toi < 12,
386
+ back_to_back: schedule.has_back_to_back,
387
+ low_ownership: ownership < 10
388
+ }
389
+ };
390
+ }
391
+ /**
392
+ * Layer 2: Check if stream is context-driven (bye/injury)
393
+ */
394
+ isContextDriven(player, gaps) {
395
+ // If user has gaps and player fills position need
396
+ if (gaps.position_needs.length > 0) {
397
+ const playerPositions = player.position.split(',');
398
+ for (const pos of playerPositions) {
399
+ if (gaps.position_needs.includes(pos)) {
400
+ return true;
401
+ }
402
+ }
403
+ }
404
+ // If significant roster gaps exist, assume context-driven
405
+ return gaps.gaps_count > 2;
406
+ }
407
+ /**
408
+ * Determine hold duration based on classification
409
+ */
410
+ determineHoldDuration(classification, metrics) {
411
+ if (classification === 'genuine' && metrics.opportunity_toi >= 16) {
412
+ return '>2 weeks';
413
+ }
414
+ if (classification === 'monitor') {
415
+ return '1-2 weeks';
416
+ }
417
+ return '<1 week';
418
+ }
419
+ /**
420
+ * Identify catalyst for stream opportunity
421
+ */
422
+ identifyCatalyst(player, metrics, schedule) {
423
+ if (metrics.pp_role === 'PP1')
424
+ return 'PP1 role lock - power play opportunity';
425
+ if (metrics.line_position === 'Top-6')
426
+ return 'Top-6 linemate upgrade';
427
+ if (schedule.game_count >= 3)
428
+ return `${schedule.game_count} games this weekend`;
429
+ if (schedule.has_back_to_back)
430
+ return 'Back-to-back games - volume play';
431
+ if (metrics.recent_ppg > 0.7)
432
+ return 'Hot streak - riding momentum';
433
+ return 'Schedule-based streaming opportunity';
434
+ }
435
+ /**
436
+ * Layer 6: Analyze fit to roster gaps
437
+ */
438
+ analyzeFit(player, gaps, classification) {
439
+ let fitReason = '';
440
+ let dropSuggestion;
441
+ // Check position fit
442
+ const playerPositions = player.position.split(',');
443
+ const matchesNeed = playerPositions.some(pos => gaps.position_needs.includes(pos));
444
+ if (matchesNeed) {
445
+ fitReason = `Fills ${gaps.position_needs.join('/')} need`;
446
+ }
447
+ else if (gaps.gaps_count > 0) {
448
+ fitReason = 'General roster depth add';
449
+ }
450
+ else {
451
+ fitReason = 'Speculative opportunity';
452
+ }
453
+ // Suggest drop for genuine opportunities
454
+ if (classification === 'genuine') {
455
+ dropSuggestion = 'Drop lowest-scoring player in position';
456
+ }
457
+ return {
458
+ fit_reason: fitReason,
459
+ drop_suggestion: dropSuggestion,
460
+ matches_position_need: matchesNeed
461
+ };
462
+ }
463
+ /**
464
+ * Summarize weekend trends
465
+ */
466
+ summarizeWeekendTrends(streams, schedules) {
467
+ const totalGames = Array.from(schedules.values())
468
+ .reduce((sum, s) => sum + s.game_count, 0);
469
+ const avgGamesPerPlayer = schedules.size > 0 ? totalGames / schedules.size : 0;
470
+ const backToBackCount = Array.from(schedules.values())
471
+ .filter(s => s.has_back_to_back).length;
472
+ return {
473
+ total_weekend_games: totalGames,
474
+ avg_games_per_player: Math.round(avgGamesPerPlayer * 10) / 10,
475
+ back_to_back_situations: backToBackCount,
476
+ top_matchup_players: streams
477
+ .filter(s => s.matchup_quality === 'elite' || s.matchup_quality === 'favorable')
478
+ .length
479
+ };
480
+ }
481
+ /**
482
+ * Generate random opponent (mock)
483
+ */
484
+ getRandomOpponent(excludeTeam) {
485
+ const teams = ['BOS', 'CAR', 'COL', 'DAL', 'EDM', 'FLA', 'NYR', 'TOR', 'VGK', 'WPG'];
486
+ const filtered = teams.filter(t => t !== excludeTeam);
487
+ return filtered[Math.floor(Math.random() * filtered.length)];
488
+ }
489
+ /**
490
+ * Generate chirp intelligence
491
+ */
492
+ async generateChirp(analysisResults, semanticContract, data) {
493
+ if (semanticContract.enable_chirp === false) {
494
+ return analysisResults;
495
+ }
496
+ // Custom chirp messages for weekend streams
497
+ const chirpMessages = this.generateWeekendChirps(analysisResults);
498
+ const enhanced = ChirpIntelligence.enhance('analyze_weekend_streams', {
499
+ ...analysisResults,
500
+ streaming_targets: analysisResults.top_genuine,
501
+ recommendations: analysisResults.top_genuine
502
+ }, semanticContract);
503
+ // Override with custom chirps
504
+ if (enhanced.chirp_intelligence) {
505
+ enhanced.chirp_intelligence.analysis_chirp = chirpMessages.main;
506
+ enhanced.chirp_intelligence.ice_cold_truth = chirpMessages.truth;
507
+ enhanced.chirp_intelligence.style = 'desperate_or_legit';
508
+ }
509
+ return enhanced;
510
+ }
511
+ /**
512
+ * Generate custom chirps for weekend streams
513
+ */
514
+ generateWeekendChirps(results) {
515
+ const { top_genuine, top_desperation, roster_gaps } = results;
516
+ let mainChirp = '';
517
+ let truth = '';
518
+ if (top_genuine.length === 0 && top_desperation.length > 5) {
519
+ mainChirp = "🆘 That's not a waiver wire, that's a cry for help. Pure desperation plays everywhere.";
520
+ truth = "Weekend streaming desperation detected. You're filling holes, not building wins.";
521
+ }
522
+ else if (top_genuine.length >= 3) {
523
+ mainChirp = `🔥 Found ${top_genuine.length} genuine opportunities. PP1 locks, top-6 roles, sustainable upside. This is how you dominate.`;
524
+ truth = "These aren't streams, they're season savers. Act fast.";
525
+ }
526
+ else if (roster_gaps.gaps_count > 3) {
527
+ mainChirp = "⚠️ Multiple roster gaps detected. You're in triage mode - prioritize high-floor plays.";
528
+ truth = "Desperation mode activated. Take the best available, worry about upside later.";
529
+ }
530
+ else {
531
+ mainChirp = "📊 Mixed bag this weekend. Some genuine plays, some desperation. Choose wisely.";
532
+ truth = "Monitor territory - hot hands with risky matchups. Tread carefully.";
533
+ }
534
+ return { main: mainChirp, truth };
535
+ }
536
+ /**
537
+ * Format final response
538
+ */
539
+ async formatResponse(chirpEnhanced, data) {
540
+ const recommendations = [];
541
+ // Add genuine opportunities as HIGH priority
542
+ for (const stream of chirpEnhanced.top_genuine || []) {
543
+ recommendations.push({
544
+ priority: 'HIGH',
545
+ action: 'pickup',
546
+ player: stream,
547
+ reasoning: `Genuine: ${stream.fit_reason} (Score: ${stream.upside_score})`
548
+ });
549
+ }
550
+ // Add monitor targets as MEDIUM priority
551
+ for (const stream of chirpEnhanced.top_monitor || []) {
552
+ recommendations.push({
553
+ priority: 'MEDIUM',
554
+ action: 'watch', // 'monitor' as watch action
555
+ player: stream,
556
+ reasoning: `Monitor: ${stream.fit_reason} (Score: ${stream.upside_score})`
557
+ });
558
+ }
559
+ const insights = {
560
+ streaming_targets: chirpEnhanced.top_genuine,
561
+ favorable_teams: [],
562
+ market_intelligence: {
563
+ total_trending: data.trendingPlayers?.length || 0,
564
+ favorable_teams_count: 0,
565
+ top_trending_team: 'Weekend Focus'
566
+ }
567
+ };
568
+ return {
569
+ analysis_insights: insights,
570
+ recommendations,
571
+ chirp_intelligence: chirpEnhanced.chirp_intelligence || this.getDefaultChirp(),
572
+ metadata: {
573
+ analysis_type: this.analysisType,
574
+ tool_identity: 'weekend_stream_analysis',
575
+ generated_at: new Date().toISOString(),
576
+ semantic_contract_applied: true,
577
+ ...(chirpEnhanced.weekend_analysis && { weekend_summary: chirpEnhanced.weekend_analysis }),
578
+ ...(chirpEnhanced.classification_breakdown && { classification_breakdown: chirpEnhanced.classification_breakdown }),
579
+ ...(chirpEnhanced.roster_gaps && { roster_gaps: chirpEnhanced.roster_gaps })
580
+ }
581
+ };
582
+ }
583
+ /**
584
+ * Default chirp when chirp intelligence is disabled
585
+ */
586
+ getDefaultChirp() {
587
+ return {
588
+ tool_identity: 'weekend_stream_analysis',
589
+ style: 'desperate_or_legit',
590
+ personality: 'tactical_analyzer',
591
+ intensity: 'ice_cold',
592
+ semantic_context: 'weekend_streaming_classification',
593
+ analysis_chirp: 'Weekend stream analysis complete - desperation vs genuine classified',
594
+ intent_summary: 'Streaming opportunities identified and categorized',
595
+ ice_cold_truth: 'Smart weekend streams build championships, desperate ones fill holes',
596
+ energy_level: 'tactical'
597
+ };
598
+ }
599
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * 🏒 Chirp Styles Configuration
3
+ *
4
+ * Defines the tone and energy levels for chirp intelligence responses.
5
+ * Each style maps to a semantic context for how the system communicates.
6
+ *
7
+ * Governance Note:
8
+ * These are semantic descriptors, not technical configurations.
9
+ * The style choice drives the semantic approach to response generation.
10
+ */
11
+ export const CHIRP_STYLES = {
12
+ gentle: {
13
+ tone: "encouraging",
14
+ energy: "supportive",
15
+ prefix: "Consider",
16
+ suffix: "when you're ready"
17
+ },
18
+ standard: {
19
+ tone: "direct_honest",
20
+ energy: "confident",
21
+ prefix: "Time to",
22
+ suffix: "and improve your game"
23
+ },
24
+ savage: {
25
+ tone: "brutal_truth",
26
+ energy: "aggressive",
27
+ prefix: "Bro,",
28
+ suffix: "Get it together!"
29
+ },
30
+ ice_cold: {
31
+ tone: "championship_enforcer",
32
+ energy: "intimidating_confidence",
33
+ prefix: "Listen up, future champion -",
34
+ suffix: "That's how legends are made."
35
+ }
36
+ };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * 🏒 Personality Modes Configuration
3
+ *
4
+ * Defines the voice and focus for different chirp intelligence personalities.
5
+ * Each mode represents a distinct semantic persona for commentary generation.
6
+ *
7
+ * Governance Note:
8
+ * These are semantic personas, not technical configurations.
9
+ * The personality choice determines the semantic approach to advice and commentary.
10
+ */
11
+ export const PERSONALITY_MODES = {
12
+ analytical: {
13
+ focus: "data_driven",
14
+ style: "smart chirps with stats backing",
15
+ voice: "hockey_statistician",
16
+ phrases: ["The data shows", "Analysis indicates", "Stats don't lie"]
17
+ },
18
+ motivational: {
19
+ focus: "championship_mindset",
20
+ style: "pump-up chirps that inspire action",
21
+ voice: "championship_coach",
22
+ phrases: ["You've got this", "Championship teams", "Winners do this"]
23
+ },
24
+ roast_master: {
25
+ focus: "entertainment_value",
26
+ style: "savage roasts with hockey humor",
27
+ voice: "locker_room_comedian",
28
+ phrases: ["Buddy,", "That's like", "Even my grandmother"]
29
+ },
30
+ championship_coach: {
31
+ focus: "winning_strategy",
32
+ style: "tough love with clear direction",
33
+ voice: "elite_level_mentor",
34
+ phrases: ["Elite players", "Championship strategy", "Next level thinking"]
35
+ }
36
+ };