@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.
- package/.env.example +8 -0
- package/LICENSE +21 -0
- package/README.md +250 -0
- package/authenticate.js +207 -0
- package/build/analyses/BreakoutAnalysis.js +386 -0
- package/build/analyses/GamesInHandAnalysis.js +257 -0
- package/build/analyses/IceAnalysis.js +316 -0
- package/build/analyses/LineupAnalysis.js +284 -0
- package/build/analyses/StreamingAnalysis.js +246 -0
- package/build/analyses/WeekendStreamAnalysis.js +599 -0
- package/build/config/chirp-styles.js +36 -0
- package/build/config/personality-modes.js +36 -0
- package/build/config/tool-metadata.js +113 -0
- package/build/domain/governance.js +322 -0
- package/build/domain/types.js +14 -0
- package/build/experimental/semantic-breakout-tool.js +188 -0
- package/build/experimental/semantic-intent-parser.js +222 -0
- package/build/experimental/semantic-tool-integration.js +146 -0
- package/build/experimental/test-parser.js +61 -0
- package/build/index.js +1549 -0
- package/build/services/ChirpIntelligence.js +213 -0
- package/build/services/YahooApiClient.js +309 -0
- package/build/template/AnalysisTemplate.js +167 -0
- package/build/types.js +2 -0
- package/package.json +50 -0
|
@@ -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
|
+
};
|