@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,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🏒❄️ ICE Analysis - Intent Chirp Engine
|
|
3
|
+
*
|
|
4
|
+
* Concrete implementation of AnalysisTemplate for roster transaction recommendations.
|
|
5
|
+
* This is the flagship "ICE" tool - championship-level optimization with savage analysis.
|
|
6
|
+
*
|
|
7
|
+
* Analysis Type: ice_roster
|
|
8
|
+
* Semantic Identity: ICE - Intent Chirp Engine
|
|
9
|
+
* Default Intensity: ice_cold
|
|
10
|
+
*/
|
|
11
|
+
import { AnalysisTemplate } from '../template/AnalysisTemplate.js';
|
|
12
|
+
import { ChirpIntelligence } from '../services/ChirpIntelligence.js';
|
|
13
|
+
/**
|
|
14
|
+
* ICE Analysis - The ultimate roster optimization engine
|
|
15
|
+
*/
|
|
16
|
+
export class IceAnalysis extends AnalysisTemplate {
|
|
17
|
+
apiClient;
|
|
18
|
+
leagueId;
|
|
19
|
+
teamId;
|
|
20
|
+
constructor(apiClient, leagueId, teamId) {
|
|
21
|
+
super("get_roster_transaction_recommendations", "ice_roster");
|
|
22
|
+
this.apiClient = apiClient;
|
|
23
|
+
this.leagueId = leagueId;
|
|
24
|
+
this.teamId = teamId;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Hook 1: Fetch raw data from Yahoo API
|
|
28
|
+
*/
|
|
29
|
+
async fetchData(args) {
|
|
30
|
+
const lookAheadDays = args.look_ahead_days || 7;
|
|
31
|
+
// Fetch all data needed for ICE analysis in parallel
|
|
32
|
+
const [rosterData, gamesInHandData, streamingData] = await Promise.all([
|
|
33
|
+
this.apiClient.getTeamRoster(this.leagueId, this.teamId),
|
|
34
|
+
this.fetchGamesInHand(),
|
|
35
|
+
this.fetchStreamingRecommendations(lookAheadDays)
|
|
36
|
+
]);
|
|
37
|
+
return {
|
|
38
|
+
roster: rosterData,
|
|
39
|
+
gamesInHand: gamesInHandData,
|
|
40
|
+
streaming: streamingData,
|
|
41
|
+
lookAheadDays
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Hook 2: Prepare and transform data for analysis
|
|
46
|
+
*/
|
|
47
|
+
async prepareData(rawData, args) {
|
|
48
|
+
// Parse roster from Yahoo API response
|
|
49
|
+
const teamArray = rawData.roster.fantasy_content.team[0];
|
|
50
|
+
const rosterData = rawData.roster.fantasy_content.team[1].roster["0"].players;
|
|
51
|
+
const teamKey = teamArray.find((item) => item.team_key)?.team_key;
|
|
52
|
+
const teamName = teamArray.find((item) => item.name)?.name;
|
|
53
|
+
// Parse players
|
|
54
|
+
const playerKeys = Object.keys(rosterData).filter(key => key !== 'count');
|
|
55
|
+
const players = playerKeys.map(key => {
|
|
56
|
+
const playerData = rosterData[key].player[0];
|
|
57
|
+
const positionData = rosterData[key].player[1];
|
|
58
|
+
const player_id = playerData.find((item) => item.player_id)?.player_id;
|
|
59
|
+
const name = playerData.find((item) => item.name)?.name?.full;
|
|
60
|
+
const position = positionData.eligible_positions?.position.join(',') || '';
|
|
61
|
+
const team = playerData.find((item) => item.editorial_team_abbr)?.editorial_team_abbr;
|
|
62
|
+
const selected_position = positionData.selected_position?.position || '';
|
|
63
|
+
const status = playerData.find((item) => item.status)?.status || '';
|
|
64
|
+
return {
|
|
65
|
+
player_id,
|
|
66
|
+
name,
|
|
67
|
+
position,
|
|
68
|
+
team,
|
|
69
|
+
selected_position,
|
|
70
|
+
status
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
// Return FantasyData with roster structure
|
|
74
|
+
// Store extra context (gamesInHand, streaming) for use in analyzeData
|
|
75
|
+
// Exclude 'roster' from spread to prevent overwriting our parsed roster
|
|
76
|
+
const { roster: _roster, ...extendedData } = rawData;
|
|
77
|
+
return {
|
|
78
|
+
roster: {
|
|
79
|
+
team_key: teamKey,
|
|
80
|
+
team_name: teamName,
|
|
81
|
+
players: players
|
|
82
|
+
},
|
|
83
|
+
// Include extended data (gamesInHand, streaming) without overwriting roster
|
|
84
|
+
...extendedData
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Hook 3: Execute core ICE analysis logic
|
|
89
|
+
*/
|
|
90
|
+
async analyzeData(data, args) {
|
|
91
|
+
// Guard: Ensure roster exists
|
|
92
|
+
if (!data.roster) {
|
|
93
|
+
throw new Error("No roster data available for analysis");
|
|
94
|
+
}
|
|
95
|
+
// Analyze current roster strengths/weaknesses
|
|
96
|
+
const rosterAnalysis = this.analyzeRosterStrengths(data);
|
|
97
|
+
// Find all transaction opportunities
|
|
98
|
+
const recommendations = [];
|
|
99
|
+
// Access extended data properties (gamesInHand, streaming)
|
|
100
|
+
const extendedData = data;
|
|
101
|
+
// 1. CRITICAL: Injured players in active lineup
|
|
102
|
+
const injuredActive = (data.roster?.players || []).filter((p) => p.status && p.status !== "" && !p.selected_position.includes("IR"));
|
|
103
|
+
for (const player of injuredActive) {
|
|
104
|
+
recommendations.push({
|
|
105
|
+
priority: "CRITICAL",
|
|
106
|
+
action: "drop", // Aligned with RecommendationAction type
|
|
107
|
+
player: player,
|
|
108
|
+
reasoning: `${player.name} is ${player.status} but still in active lineup - move to IR`
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
// 2. HIGH: Position weakness fixes
|
|
112
|
+
const weakPositions = this.identifyWeakPositions(data, rosterAnalysis);
|
|
113
|
+
for (const position of weakPositions) {
|
|
114
|
+
const bestAvailable = extendedData.streaming?.streaming_targets
|
|
115
|
+
?.filter((p) => args.target_positions ? args.target_positions.includes(position.position) : true)
|
|
116
|
+
.filter((p) => p.position.includes(position.position))
|
|
117
|
+
.slice(0, 3) || [];
|
|
118
|
+
if (bestAvailable.length > 0) {
|
|
119
|
+
const dropCandidate = this.findBestDropCandidate(data, position.position);
|
|
120
|
+
recommendations.push({
|
|
121
|
+
priority: "HIGH",
|
|
122
|
+
action: "pickup", // Aligned with RecommendationAction type
|
|
123
|
+
pickup: bestAvailable[0],
|
|
124
|
+
drop: dropCandidate,
|
|
125
|
+
reasoning: `Strengthen ${position.position} - ${position.weakness_reason}`
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// 3. MEDIUM: Schedule optimization
|
|
130
|
+
const gamesDiff = extendedData.gamesInHand?.games_in_hand_difference || 0;
|
|
131
|
+
if (gamesDiff < 0) {
|
|
132
|
+
const volumePickups = extendedData.streaming?.streaming_targets
|
|
133
|
+
?.filter((t) => t.team_trending_count >= 3)
|
|
134
|
+
.slice(0, 2) || [];
|
|
135
|
+
for (const pickup of volumePickups) {
|
|
136
|
+
recommendations.push({
|
|
137
|
+
priority: "MEDIUM",
|
|
138
|
+
action: "volume_play",
|
|
139
|
+
pickup: pickup,
|
|
140
|
+
reasoning: `Opponent has ${Math.abs(gamesDiff)} more games - need volume players`
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// 4. Bench optimizations
|
|
145
|
+
const benchUpgrades = this.findBenchUpgrades(data);
|
|
146
|
+
recommendations.push(...benchUpgrades);
|
|
147
|
+
// Sort by priority
|
|
148
|
+
const sortedRecommendations = recommendations
|
|
149
|
+
.sort((a, b) => {
|
|
150
|
+
const priorityOrder = {
|
|
151
|
+
"CRITICAL": 0,
|
|
152
|
+
"HIGH": 1,
|
|
153
|
+
"MEDIUM": 2,
|
|
154
|
+
"LOW": 3
|
|
155
|
+
};
|
|
156
|
+
return (priorityOrder[a.priority] || 99) - (priorityOrder[b.priority] || 99);
|
|
157
|
+
})
|
|
158
|
+
.slice(0, 8); // Top 8 recommendations
|
|
159
|
+
return {
|
|
160
|
+
roster_analysis: rosterAnalysis,
|
|
161
|
+
immediate_issues: injuredActive.length,
|
|
162
|
+
games_disadvantage: gamesDiff,
|
|
163
|
+
weak_positions: weakPositions,
|
|
164
|
+
recommendations: sortedRecommendations,
|
|
165
|
+
optimal_timing: extendedData.streaming?.optimal_timing,
|
|
166
|
+
market_intelligence: extendedData.streaming?.market_intelligence
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Hook 4: Generate chirp intelligence commentary
|
|
171
|
+
*/
|
|
172
|
+
async generateChirp(analysisResults, semanticContract, data) {
|
|
173
|
+
// Use ChirpIntelligence service to enhance results
|
|
174
|
+
return ChirpIntelligence.enhance(this.toolName, analysisResults, semanticContract);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Hook 5: Format final response structure
|
|
178
|
+
*/
|
|
179
|
+
async formatResponse(chirpEnhanced, data) {
|
|
180
|
+
// Format insights according to AnalysisInsights interface
|
|
181
|
+
const insights = {
|
|
182
|
+
immediate_issues: chirpEnhanced.immediate_issues || 0,
|
|
183
|
+
games_disadvantage: chirpEnhanced.games_disadvantage || 0,
|
|
184
|
+
weak_positions: chirpEnhanced.weak_positions || [],
|
|
185
|
+
optimal_timing: chirpEnhanced.optimal_timing,
|
|
186
|
+
market_intelligence: chirpEnhanced.market_intelligence
|
|
187
|
+
};
|
|
188
|
+
return {
|
|
189
|
+
analysis_insights: insights,
|
|
190
|
+
recommendations: chirpEnhanced.recommendations,
|
|
191
|
+
chirp_intelligence: chirpEnhanced.chirp_intelligence,
|
|
192
|
+
metadata: chirpEnhanced.metadata
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
// ==========================================
|
|
196
|
+
// Private Helper Methods
|
|
197
|
+
// ==========================================
|
|
198
|
+
analyzeRosterStrengths(data) {
|
|
199
|
+
const positions = {
|
|
200
|
+
C: [], LW: [], RW: [], D: [], G: [],
|
|
201
|
+
bench: [], ir: [], active: [],
|
|
202
|
+
position_counts: { C: 0, LW: 0, RW: 0, D: 0, G: 0, bench: 0 },
|
|
203
|
+
strength_score: 0,
|
|
204
|
+
weakest_position: ""
|
|
205
|
+
};
|
|
206
|
+
if (!data.roster || !data.roster.players)
|
|
207
|
+
return positions;
|
|
208
|
+
data.roster.players.forEach((player) => {
|
|
209
|
+
if (player.selected_position === "BN") {
|
|
210
|
+
positions.bench.push(player);
|
|
211
|
+
}
|
|
212
|
+
else if (player.selected_position.includes("IR")) {
|
|
213
|
+
positions.ir.push(player);
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
positions.active.push(player);
|
|
217
|
+
if (player.position.includes("C"))
|
|
218
|
+
positions.C.push(player);
|
|
219
|
+
if (player.position.includes("LW"))
|
|
220
|
+
positions.LW.push(player);
|
|
221
|
+
if (player.position.includes("RW"))
|
|
222
|
+
positions.RW.push(player);
|
|
223
|
+
if (player.position.includes("D"))
|
|
224
|
+
positions.D.push(player);
|
|
225
|
+
if (player.position.includes("G"))
|
|
226
|
+
positions.G.push(player);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
positions.position_counts = {
|
|
230
|
+
C: positions.C.length,
|
|
231
|
+
LW: positions.LW.length,
|
|
232
|
+
RW: positions.RW.length,
|
|
233
|
+
D: positions.D.length,
|
|
234
|
+
G: positions.G.length,
|
|
235
|
+
bench: positions.bench.length
|
|
236
|
+
};
|
|
237
|
+
// Calculate strength score (simple heuristic)
|
|
238
|
+
positions.strength_score = Object.values(positions.position_counts).reduce((a, b) => a + b, 0);
|
|
239
|
+
// Find weakest position
|
|
240
|
+
const positionCounts = positions.position_counts;
|
|
241
|
+
positions.weakest_position = Object.entries(positionCounts)
|
|
242
|
+
.filter(([pos]) => pos !== 'bench')
|
|
243
|
+
.sort(([, a], [, b]) => a - b)[0]?.[0] || "";
|
|
244
|
+
return positions;
|
|
245
|
+
}
|
|
246
|
+
identifyWeakPositions(data, analysis) {
|
|
247
|
+
const weakPositions = [];
|
|
248
|
+
const avgPerPosition = analysis.strength_score / 5;
|
|
249
|
+
for (const [position, count] of Object.entries(analysis.position_counts)) {
|
|
250
|
+
if (position !== 'bench' && count < avgPerPosition * 0.7) {
|
|
251
|
+
weakPositions.push({
|
|
252
|
+
position,
|
|
253
|
+
current_count: count,
|
|
254
|
+
weakness_reason: `Only ${count} players vs average of ${avgPerPosition.toFixed(1)}`
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return weakPositions;
|
|
259
|
+
}
|
|
260
|
+
findBestDropCandidate(data, position) {
|
|
261
|
+
if (!data.roster || !data.roster.players)
|
|
262
|
+
return null;
|
|
263
|
+
const benchPlayers = data.roster.players.filter((p) => p.selected_position === "BN");
|
|
264
|
+
return benchPlayers.length > 0 ? benchPlayers[0] : null;
|
|
265
|
+
}
|
|
266
|
+
findBenchUpgrades(data) {
|
|
267
|
+
const recommendations = [];
|
|
268
|
+
if (!data.roster || !data.roster.players)
|
|
269
|
+
return recommendations;
|
|
270
|
+
const extendedData = data;
|
|
271
|
+
const benchPlayers = data.roster.players.filter((p) => p.selected_position === "BN");
|
|
272
|
+
// Simple heuristic: suggest top streaming targets for bench slots
|
|
273
|
+
const topTargets = extendedData.streaming?.streaming_targets?.slice(0, Math.min(2, benchPlayers.length)) || [];
|
|
274
|
+
for (let i = 0; i < topTargets.length; i++) {
|
|
275
|
+
const target = topTargets[i];
|
|
276
|
+
const benchPlayer = benchPlayers[i];
|
|
277
|
+
if (benchPlayer) {
|
|
278
|
+
recommendations.push({
|
|
279
|
+
priority: "LOW",
|
|
280
|
+
action: "bench_upgrade",
|
|
281
|
+
pickup: target,
|
|
282
|
+
drop: benchPlayer,
|
|
283
|
+
reasoning: `Upgrade bench: ${target.name} trending better than ${benchPlayer.name}`
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return recommendations;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Temporary stub for games in hand (to be migrated to its own analysis)
|
|
291
|
+
*/
|
|
292
|
+
async fetchGamesInHand() {
|
|
293
|
+
// TODO: Replace with proper GamesInHandAnalysis when migrated
|
|
294
|
+
return {
|
|
295
|
+
games_in_hand_difference: 0,
|
|
296
|
+
your_remaining: 0,
|
|
297
|
+
opponent_remaining: 0
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Temporary stub for streaming recommendations (to be migrated)
|
|
302
|
+
*/
|
|
303
|
+
async fetchStreamingRecommendations(lookAheadDays) {
|
|
304
|
+
// TODO: Replace with proper StreamingAnalysis when migrated
|
|
305
|
+
return {
|
|
306
|
+
streaming_targets: [],
|
|
307
|
+
optimal_timing: {
|
|
308
|
+
best_days: [],
|
|
309
|
+
avoid_days: []
|
|
310
|
+
},
|
|
311
|
+
market_intelligence: {
|
|
312
|
+
top_trending_team: "unknown"
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* LineupAnalysis - Template Method Pattern Implementation
|
|
4
|
+
*
|
|
5
|
+
* Optimizes daily lineup by identifying benched players who should be active
|
|
6
|
+
* and active players who should be benched.
|
|
7
|
+
*/
|
|
8
|
+
import { AnalysisTemplate } from '../template/AnalysisTemplate.js';
|
|
9
|
+
import { ChirpIntelligence } from '../services/ChirpIntelligence.js';
|
|
10
|
+
export class LineupAnalysis extends AnalysisTemplate {
|
|
11
|
+
apiClient;
|
|
12
|
+
leagueId;
|
|
13
|
+
teamId;
|
|
14
|
+
constructor(apiClient, leagueId, teamId) {
|
|
15
|
+
super("optimize_lineup", "lineup_optimization");
|
|
16
|
+
this.apiClient = apiClient;
|
|
17
|
+
this.leagueId = leagueId;
|
|
18
|
+
this.teamId = teamId;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Hook 1: Fetch raw data from Yahoo API
|
|
22
|
+
*/
|
|
23
|
+
async fetchData(args) {
|
|
24
|
+
try {
|
|
25
|
+
// Fetch current roster with positions
|
|
26
|
+
const rosterData = await this.apiClient.getTeamRoster(this.leagueId, this.teamId);
|
|
27
|
+
// Fetch today's scoreboard to see who's playing
|
|
28
|
+
const scoreboardData = await this.apiClient.getLeagueScoreboard(this.leagueId);
|
|
29
|
+
return {
|
|
30
|
+
roster: rosterData,
|
|
31
|
+
scoreboard: scoreboardData
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
throw new Error(`Failed to fetch lineup data: ${error}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Hook 2: Prepare data into FantasyData structure
|
|
40
|
+
*/
|
|
41
|
+
async prepareData(rawData, args) {
|
|
42
|
+
const { roster, scoreboard } = rawData;
|
|
43
|
+
const teamArray = roster.fantasy_content?.team?.[0] || [];
|
|
44
|
+
const rosterData = roster.fantasy_content?.team?.[1]?.roster?.['0']?.players || {};
|
|
45
|
+
// Fetch today's NHL schedule once for all players
|
|
46
|
+
const todaySchedule = await this.fetchTodaySchedule();
|
|
47
|
+
// Use object key iteration like IceAnalysis for consistent player parsing
|
|
48
|
+
const playerKeys = Object.keys(rosterData).filter(key => key !== 'count');
|
|
49
|
+
// Parse player data first (synchronous)
|
|
50
|
+
const parsedPlayers = playerKeys.map(key => {
|
|
51
|
+
// Yahoo API returns player data as nested arrays
|
|
52
|
+
// player[0] is an array of objects, player[1] contains position info
|
|
53
|
+
const player = rosterData[key].player?.[0] || rosterData[key];
|
|
54
|
+
const positionInfo = rosterData[key].player?.[1] || {};
|
|
55
|
+
// Use .find() pattern like IceAnalysis since player is an array of property objects
|
|
56
|
+
const player_id = Array.isArray(player)
|
|
57
|
+
? player.find((item) => item.player_id)?.player_id
|
|
58
|
+
: player.player_id;
|
|
59
|
+
const name = Array.isArray(player)
|
|
60
|
+
? player.find((item) => item.name)?.name?.full
|
|
61
|
+
: player.name?.full;
|
|
62
|
+
const position = Array.isArray(player)
|
|
63
|
+
? player.find((item) => item.display_position)?.display_position ||
|
|
64
|
+
player.find((item) => item.primary_positions)?.primary_positions?.[0]
|
|
65
|
+
: player.display_position || player.primary_positions?.[0];
|
|
66
|
+
const team = Array.isArray(player)
|
|
67
|
+
? player.find((item) => item.editorial_team_abbr)?.editorial_team_abbr
|
|
68
|
+
: player.editorial_team_abbr;
|
|
69
|
+
const status = Array.isArray(player)
|
|
70
|
+
? player.find((item) => item.status)?.status
|
|
71
|
+
: player.status;
|
|
72
|
+
const selectedPosition = positionInfo.selected_position?.[1]?.position ||
|
|
73
|
+
positionInfo.selected_position?.position ||
|
|
74
|
+
'BN';
|
|
75
|
+
return {
|
|
76
|
+
player_id: player_id || '',
|
|
77
|
+
name: name || 'Unknown',
|
|
78
|
+
position: position || 'Unknown',
|
|
79
|
+
team: team || '',
|
|
80
|
+
selected_position: Array.isArray(selectedPosition) ? selectedPosition : [selectedPosition],
|
|
81
|
+
status: status || ''
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
// Add has_game_today flag using the schedule we fetched
|
|
85
|
+
const players = parsedPlayers.map((player) => ({
|
|
86
|
+
...player,
|
|
87
|
+
has_game_today: this.hasGameToday(player, todaySchedule)
|
|
88
|
+
}));
|
|
89
|
+
// Extract team info using .find() pattern like IceAnalysis
|
|
90
|
+
const team_key = Array.isArray(teamArray)
|
|
91
|
+
? teamArray.find((item) => item.team_key)?.team_key
|
|
92
|
+
: teamArray.team_key;
|
|
93
|
+
const team_name = Array.isArray(teamArray)
|
|
94
|
+
? teamArray.find((item) => item.name)?.name
|
|
95
|
+
: teamArray.name;
|
|
96
|
+
return {
|
|
97
|
+
roster: {
|
|
98
|
+
team_key: team_key || '',
|
|
99
|
+
team_name: team_name || 'Your Team',
|
|
100
|
+
players
|
|
101
|
+
},
|
|
102
|
+
scoreboard: scoreboard
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Hook 3: Analyze data to identify lineup issues
|
|
107
|
+
*/
|
|
108
|
+
async analyzeData(data, args) {
|
|
109
|
+
const issues = [];
|
|
110
|
+
const players = data.roster.players;
|
|
111
|
+
// Issue 1: CRITICAL - Injured players in active lineup
|
|
112
|
+
const injuredActive = players.filter(p => p.status &&
|
|
113
|
+
p.status !== 'Healthy' &&
|
|
114
|
+
!p.selected_position.includes('IR') &&
|
|
115
|
+
!p.selected_position.includes('BN'));
|
|
116
|
+
for (const player of injuredActive) {
|
|
117
|
+
issues.push({
|
|
118
|
+
type: 'active_injured',
|
|
119
|
+
severity: 'CRITICAL',
|
|
120
|
+
player,
|
|
121
|
+
recommendation: `Move ${player.name} (${player.status}) to IR or bench immediately`
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
// Issue 2: HIGH - Healthy players on bench with games today
|
|
125
|
+
const benchedWithGames = players.filter(p => p.selected_position.includes('BN') &&
|
|
126
|
+
p.has_game_today &&
|
|
127
|
+
(!p.status || p.status === 'Healthy'));
|
|
128
|
+
for (const player of benchedWithGames) {
|
|
129
|
+
issues.push({
|
|
130
|
+
type: 'benched_active',
|
|
131
|
+
severity: 'HIGH',
|
|
132
|
+
player,
|
|
133
|
+
recommendation: `${player.name} is benched but has a game today - activate if possible`
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
// Issue 3: MEDIUM - Active players without games today
|
|
137
|
+
const activeNoGames = players.filter(p => !p.selected_position.includes('BN') &&
|
|
138
|
+
!p.selected_position.includes('IR') &&
|
|
139
|
+
!p.has_game_today);
|
|
140
|
+
const benchedHealthy = players.filter(p => p.selected_position.includes('BN') &&
|
|
141
|
+
p.has_game_today &&
|
|
142
|
+
(!p.status || p.status === 'Healthy'));
|
|
143
|
+
if (activeNoGames.length > 0 && benchedHealthy.length > 0) {
|
|
144
|
+
// Suggest swaps
|
|
145
|
+
const swapCount = Math.min(activeNoGames.length, benchedHealthy.length);
|
|
146
|
+
for (let i = 0; i < swapCount; i++) {
|
|
147
|
+
issues.push({
|
|
148
|
+
type: 'position_inefficiency',
|
|
149
|
+
severity: 'MEDIUM',
|
|
150
|
+
recommendation: `Swap ${activeNoGames[i].name} (no game) with ${benchedHealthy[i].name} (playing)`
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Issue 4: LOW - Empty active slots that could be filled
|
|
155
|
+
const activeSlots = players.filter(p => !p.selected_position.includes('BN') &&
|
|
156
|
+
!p.selected_position.includes('IR')).length;
|
|
157
|
+
const benchPlayers = players.filter(p => p.selected_position.includes('BN')).length;
|
|
158
|
+
// Estimate if there are likely empty slots (simplified)
|
|
159
|
+
// In production, would check actual league roster settings
|
|
160
|
+
const estimatedMaxActiveSlots = 12; // Typical fantasy hockey active roster size
|
|
161
|
+
if (activeSlots < estimatedMaxActiveSlots && benchPlayers > 0) {
|
|
162
|
+
issues.push({
|
|
163
|
+
type: 'empty_slot',
|
|
164
|
+
severity: 'LOW',
|
|
165
|
+
recommendation: `You may have empty active slots - review your lineup`
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return issues;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Hook 4: Generate chirp-enhanced response
|
|
172
|
+
*/
|
|
173
|
+
async generateChirp(analysisResults, semanticContract, data) {
|
|
174
|
+
return ChirpIntelligence.enhance(this.toolName, { lineup_issues: analysisResults }, semanticContract);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Hook 5: Format final response
|
|
178
|
+
*/
|
|
179
|
+
async formatResponse(chirpEnhanced, data) {
|
|
180
|
+
const lineupIssues = chirpEnhanced.lineup_issues;
|
|
181
|
+
// Convert to standard Recommendation format
|
|
182
|
+
const recommendations = lineupIssues.map(issue => {
|
|
183
|
+
// Map issue type to action
|
|
184
|
+
let action = 'lineup_change';
|
|
185
|
+
if (issue.type === 'active_injured') {
|
|
186
|
+
action = 'move_to_ir';
|
|
187
|
+
}
|
|
188
|
+
else if (issue.type === 'benched_active') {
|
|
189
|
+
action = 'lineup_change';
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
priority: issue.severity,
|
|
193
|
+
action,
|
|
194
|
+
player: issue.player,
|
|
195
|
+
reasoning: issue.recommendation
|
|
196
|
+
};
|
|
197
|
+
});
|
|
198
|
+
const criticalCount = lineupIssues.filter(i => i.severity === 'CRITICAL').length;
|
|
199
|
+
const highCount = lineupIssues.filter(i => i.severity === 'HIGH').length;
|
|
200
|
+
const analysisInsights = {
|
|
201
|
+
lineup_health: {
|
|
202
|
+
total_issues: lineupIssues.length,
|
|
203
|
+
critical_issues: criticalCount,
|
|
204
|
+
high_priority_issues: highCount,
|
|
205
|
+
lineup_score: this.calculateLineupScore(lineupIssues)
|
|
206
|
+
},
|
|
207
|
+
immediate_actions: lineupIssues
|
|
208
|
+
.filter(i => i.severity === 'CRITICAL' || i.severity === 'HIGH')
|
|
209
|
+
.map(i => i.recommendation)
|
|
210
|
+
};
|
|
211
|
+
const metadata = {
|
|
212
|
+
analysis_type: this.analysisType,
|
|
213
|
+
timestamp: new Date().toISOString(),
|
|
214
|
+
team_context: {
|
|
215
|
+
team_name: data.roster.team_name
|
|
216
|
+
},
|
|
217
|
+
semantic_contract_applied: true
|
|
218
|
+
};
|
|
219
|
+
return {
|
|
220
|
+
analysis_insights: analysisInsights,
|
|
221
|
+
recommendations,
|
|
222
|
+
chirp_intelligence: chirpEnhanced.chirp_intelligence,
|
|
223
|
+
metadata
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Helper: Fetch today's NHL schedule from public API
|
|
228
|
+
* Returns array of games for efficient lookup
|
|
229
|
+
*/
|
|
230
|
+
async fetchTodaySchedule() {
|
|
231
|
+
try {
|
|
232
|
+
// Get today's date in YYYY-MM-DD format
|
|
233
|
+
const today = new Date().toISOString().split('T')[0];
|
|
234
|
+
// Fetch today's NHL schedule from public NHL API
|
|
235
|
+
const response = await fetch(`https://api-web.nhle.com/v1/schedule/${today}`);
|
|
236
|
+
if (!response.ok)
|
|
237
|
+
return [];
|
|
238
|
+
const schedule = await response.json();
|
|
239
|
+
return schedule.gameWeek?.[0]?.games || [];
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
console.error('[DEBUG] Error fetching NHL schedule:', error);
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Helper: Check if player has a game today
|
|
248
|
+
* Uses pre-fetched NHL schedule for efficiency
|
|
249
|
+
*/
|
|
250
|
+
hasGameToday(player, todayGames) {
|
|
251
|
+
if (!player.team || !todayGames.length)
|
|
252
|
+
return false;
|
|
253
|
+
const playerTeam = player.team.toUpperCase();
|
|
254
|
+
return todayGames.some((game) => {
|
|
255
|
+
const homeTeam = game.homeTeam?.abbrev?.toUpperCase();
|
|
256
|
+
const awayTeam = game.awayTeam?.abbrev?.toUpperCase();
|
|
257
|
+
return homeTeam === playerTeam || awayTeam === playerTeam;
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Helper: Calculate overall lineup health score (0-100)
|
|
262
|
+
*/
|
|
263
|
+
calculateLineupScore(issues) {
|
|
264
|
+
let score = 100;
|
|
265
|
+
// Deduct points for each issue based on severity
|
|
266
|
+
for (const issue of issues) {
|
|
267
|
+
switch (issue.severity) {
|
|
268
|
+
case 'CRITICAL':
|
|
269
|
+
score -= 20;
|
|
270
|
+
break;
|
|
271
|
+
case 'HIGH':
|
|
272
|
+
score -= 10;
|
|
273
|
+
break;
|
|
274
|
+
case 'MEDIUM':
|
|
275
|
+
score -= 5;
|
|
276
|
+
break;
|
|
277
|
+
case 'LOW':
|
|
278
|
+
score -= 2;
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return Math.max(0, score);
|
|
283
|
+
}
|
|
284
|
+
}
|