universal-mcp-applications 0.1.32__py3-none-any.whl → 0.1.36rc2__py3-none-any.whl
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.
- universal_mcp/applications/ahrefs/app.py +52 -198
- universal_mcp/applications/airtable/app.py +23 -122
- universal_mcp/applications/apollo/app.py +111 -464
- universal_mcp/applications/asana/app.py +417 -1567
- universal_mcp/applications/aws_s3/app.py +36 -103
- universal_mcp/applications/bill/app.py +546 -1957
- universal_mcp/applications/box/app.py +1068 -3981
- universal_mcp/applications/braze/app.py +364 -1430
- universal_mcp/applications/browser_use/app.py +2 -8
- universal_mcp/applications/cal_com_v2/app.py +207 -625
- universal_mcp/applications/calendly/app.py +61 -200
- universal_mcp/applications/canva/app.py +45 -110
- universal_mcp/applications/clickup/app.py +207 -674
- universal_mcp/applications/coda/app.py +146 -426
- universal_mcp/applications/confluence/app.py +310 -1098
- universal_mcp/applications/contentful/app.py +36 -151
- universal_mcp/applications/crustdata/app.py +28 -107
- universal_mcp/applications/dialpad/app.py +283 -756
- universal_mcp/applications/digitalocean/app.py +1766 -5777
- universal_mcp/applications/domain_checker/app.py +3 -54
- universal_mcp/applications/e2b/app.py +14 -64
- universal_mcp/applications/elevenlabs/app.py +9 -47
- universal_mcp/applications/exa/app.py +6 -17
- universal_mcp/applications/falai/app.py +24 -101
- universal_mcp/applications/figma/app.py +53 -137
- universal_mcp/applications/file_system/app.py +2 -13
- universal_mcp/applications/firecrawl/app.py +51 -152
- universal_mcp/applications/fireflies/app.py +59 -281
- universal_mcp/applications/fpl/app.py +91 -528
- universal_mcp/applications/fpl/utils/fixtures.py +15 -49
- universal_mcp/applications/fpl/utils/helper.py +25 -89
- universal_mcp/applications/fpl/utils/league_utils.py +20 -64
- universal_mcp/applications/ghost_content/app.py +52 -161
- universal_mcp/applications/github/app.py +19 -56
- universal_mcp/applications/gong/app.py +88 -248
- universal_mcp/applications/google_calendar/app.py +16 -68
- universal_mcp/applications/google_docs/app.py +85 -189
- universal_mcp/applications/google_drive/app.py +141 -463
- universal_mcp/applications/google_gemini/app.py +12 -64
- universal_mcp/applications/google_mail/app.py +28 -157
- universal_mcp/applications/google_searchconsole/app.py +15 -48
- universal_mcp/applications/google_sheet/app.py +100 -581
- universal_mcp/applications/google_sheet/helper.py +10 -37
- universal_mcp/applications/hashnode/app.py +57 -269
- universal_mcp/applications/heygen/app.py +44 -122
- universal_mcp/applications/http_tools/app.py +10 -32
- universal_mcp/applications/hubspot/api_segments/crm_api.py +460 -1573
- universal_mcp/applications/hubspot/api_segments/marketing_api.py +74 -262
- universal_mcp/applications/hubspot/app.py +23 -87
- universal_mcp/applications/jira/app.py +2071 -7986
- universal_mcp/applications/klaviyo/app.py +494 -1376
- universal_mcp/applications/linkedin/README.md +9 -2
- universal_mcp/applications/linkedin/app.py +240 -181
- universal_mcp/applications/mailchimp/app.py +450 -1605
- universal_mcp/applications/markitdown/app.py +8 -20
- universal_mcp/applications/miro/app.py +217 -699
- universal_mcp/applications/ms_teams/app.py +64 -186
- universal_mcp/applications/neon/app.py +86 -192
- universal_mcp/applications/notion/app.py +21 -36
- universal_mcp/applications/onedrive/app.py +16 -38
- universal_mcp/applications/openai/app.py +42 -165
- universal_mcp/applications/outlook/app.py +24 -84
- universal_mcp/applications/perplexity/app.py +4 -19
- universal_mcp/applications/pipedrive/app.py +832 -3142
- universal_mcp/applications/posthog/app.py +163 -432
- universal_mcp/applications/reddit/app.py +40 -139
- universal_mcp/applications/resend/app.py +41 -107
- universal_mcp/applications/retell/app.py +14 -41
- universal_mcp/applications/rocketlane/app.py +221 -934
- universal_mcp/applications/scraper/README.md +7 -4
- universal_mcp/applications/scraper/app.py +50 -109
- universal_mcp/applications/semanticscholar/app.py +22 -64
- universal_mcp/applications/semrush/app.py +43 -77
- universal_mcp/applications/sendgrid/app.py +512 -1262
- universal_mcp/applications/sentry/app.py +271 -906
- universal_mcp/applications/serpapi/app.py +40 -143
- universal_mcp/applications/sharepoint/app.py +17 -39
- universal_mcp/applications/shopify/app.py +1551 -4287
- universal_mcp/applications/shortcut/app.py +155 -417
- universal_mcp/applications/slack/app.py +33 -115
- universal_mcp/applications/spotify/app.py +126 -325
- universal_mcp/applications/supabase/app.py +104 -213
- universal_mcp/applications/tavily/app.py +1 -1
- universal_mcp/applications/trello/app.py +693 -2656
- universal_mcp/applications/twilio/app.py +14 -50
- universal_mcp/applications/twitter/api_segments/compliance_api.py +4 -14
- universal_mcp/applications/twitter/api_segments/dm_conversations_api.py +6 -18
- universal_mcp/applications/twitter/api_segments/likes_api.py +1 -3
- universal_mcp/applications/twitter/api_segments/lists_api.py +5 -15
- universal_mcp/applications/twitter/api_segments/trends_api.py +1 -3
- universal_mcp/applications/twitter/api_segments/tweets_api.py +9 -31
- universal_mcp/applications/twitter/api_segments/usage_api.py +1 -5
- universal_mcp/applications/twitter/api_segments/users_api.py +14 -42
- universal_mcp/applications/whatsapp/app.py +35 -186
- universal_mcp/applications/whatsapp/audio.py +2 -6
- universal_mcp/applications/whatsapp/whatsapp.py +17 -51
- universal_mcp/applications/whatsapp_business/app.py +70 -283
- universal_mcp/applications/wrike/app.py +45 -118
- universal_mcp/applications/yahoo_finance/app.py +19 -65
- universal_mcp/applications/youtube/app.py +75 -261
- universal_mcp/applications/zenquotes/app.py +2 -2
- {universal_mcp_applications-0.1.32.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/METADATA +2 -2
- {universal_mcp_applications-0.1.32.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/RECORD +105 -105
- {universal_mcp_applications-0.1.32.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/WHEEL +0 -0
- {universal_mcp_applications-0.1.32.dist-info → universal_mcp_applications-0.1.36rc2.dist-info}/licenses/LICENSE +0 -0
|
@@ -9,9 +9,7 @@ from .api import api
|
|
|
9
9
|
logger = logging.getLogger("fpl-mcp-server.fixtures")
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def get_fixtures_resource(
|
|
13
|
-
gameweek_id: int | None = None, team_name: str | None = None
|
|
14
|
-
) -> list[dict[str, Any]]:
|
|
12
|
+
def get_fixtures_resource(gameweek_id: int | None = None, team_name: str | None = None) -> list[dict[str, Any]]:
|
|
15
13
|
"""Get fixtures from the FPL API with optional filtering by gameweek or team
|
|
16
14
|
|
|
17
15
|
Args:
|
|
@@ -68,9 +66,7 @@ def get_fixtures_resource(
|
|
|
68
66
|
|
|
69
67
|
# Apply gameweek filter if provided
|
|
70
68
|
if gameweek_id is not None:
|
|
71
|
-
formatted_fixtures = [
|
|
72
|
-
f for f in formatted_fixtures if f["gameweek"] == gameweek_id
|
|
73
|
-
]
|
|
69
|
+
formatted_fixtures = [f for f in formatted_fixtures if f["gameweek"] == gameweek_id]
|
|
74
70
|
|
|
75
71
|
# Apply team filter if provided
|
|
76
72
|
if team_name is not None:
|
|
@@ -109,9 +105,7 @@ def get_player_fixtures(player_id: int, num_fixtures: int = 5) -> list[dict[str,
|
|
|
109
105
|
Returns:
|
|
110
106
|
List of upcoming fixtures for the player
|
|
111
107
|
"""
|
|
112
|
-
logger.info(
|
|
113
|
-
f"Getting player fixtures (player_id={player_id}, num_fixtures={num_fixtures})"
|
|
114
|
-
)
|
|
108
|
+
logger.info(f"Getting player fixtures (player_id={player_id}, num_fixtures={num_fixtures})")
|
|
115
109
|
|
|
116
110
|
# Get player data to find their team
|
|
117
111
|
players_data = api.get_players()
|
|
@@ -190,9 +184,7 @@ def get_player_fixtures(player_id: int, num_fixtures: int = 5) -> list[dict[str,
|
|
|
190
184
|
opponent_team = team_map.get(opponent_id, {})
|
|
191
185
|
|
|
192
186
|
# Determine difficulty - higher is more difficult
|
|
193
|
-
difficulty = fixture.get(
|
|
194
|
-
"team_h_difficulty" if is_home else "team_a_difficulty", 3
|
|
195
|
-
)
|
|
187
|
+
difficulty = fixture.get("team_h_difficulty" if is_home else "team_a_difficulty", 3)
|
|
196
188
|
|
|
197
189
|
formatted_fixture = {
|
|
198
190
|
"gameweek": fixture.get("event"),
|
|
@@ -218,9 +210,7 @@ def analyze_player_fixtures(player_id: int, num_fixtures: int = 5) -> dict[str,
|
|
|
218
210
|
Returns:
|
|
219
211
|
Analysis of player's upcoming fixtures with difficulty ratings
|
|
220
212
|
"""
|
|
221
|
-
logger.info(
|
|
222
|
-
f"Analyzing player fixtures (player_id={player_id}, num_fixtures={num_fixtures})"
|
|
223
|
-
)
|
|
213
|
+
logger.info(f"Analyzing player fixtures (player_id={player_id}, num_fixtures={num_fixtures})")
|
|
224
214
|
|
|
225
215
|
# Get player data
|
|
226
216
|
players_data = api.get_players()
|
|
@@ -258,9 +248,7 @@ def analyze_player_fixtures(player_id: int, num_fixtures: int = 5) -> dict[str,
|
|
|
258
248
|
position_info = position_map.get(position_id, {})
|
|
259
249
|
position_code = position_info.get("singular_name_short", "Unknown position")
|
|
260
250
|
|
|
261
|
-
logger.info(
|
|
262
|
-
"Player %s plays as %s for %s", player.get("web_name"), position_code, team_name
|
|
263
|
-
)
|
|
251
|
+
logger.info("Player %s plays as %s for %s", player.get("web_name"), position_code, team_name)
|
|
264
252
|
|
|
265
253
|
# Make sure position is one of GK, DEF, MID, FWD
|
|
266
254
|
position_mapping = {"GKP": "GK", "DEF": "DEF", "MID": "MID", "FWD": "FWD"}
|
|
@@ -360,11 +348,7 @@ def get_blank_gameweeks(num_gameweeks: int = 5) -> list[dict[str, Any]]:
|
|
|
360
348
|
current_gw_id = current_gw["id"]
|
|
361
349
|
|
|
362
350
|
# Limit to specified number of upcoming gameweeks
|
|
363
|
-
upcoming_gameweeks = [
|
|
364
|
-
gw
|
|
365
|
-
for gw in all_gameweeks
|
|
366
|
-
if gw["id"] >= current_gw_id and gw["id"] < current_gw_id + num_gameweeks
|
|
367
|
-
]
|
|
351
|
+
upcoming_gameweeks = [gw for gw in all_gameweeks if gw["id"] >= current_gw_id and gw["id"] < current_gw_id + num_gameweeks]
|
|
368
352
|
|
|
369
353
|
# Map team IDs to names
|
|
370
354
|
team_map = {t["id"]: t for t in team_data}
|
|
@@ -439,11 +423,7 @@ def get_double_gameweeks(num_gameweeks: int = 5) -> list[dict[str, Any]]:
|
|
|
439
423
|
current_gw_id = current_gw["id"]
|
|
440
424
|
|
|
441
425
|
# Limit to specified number of upcoming gameweeks
|
|
442
|
-
upcoming_gameweeks = [
|
|
443
|
-
gw
|
|
444
|
-
for gw in all_gameweeks
|
|
445
|
-
if gw["id"] >= current_gw_id and gw["id"] < current_gw_id + num_gameweeks
|
|
446
|
-
]
|
|
426
|
+
upcoming_gameweeks = [gw for gw in all_gameweeks if gw["id"] >= current_gw_id and gw["id"] < current_gw_id + num_gameweeks]
|
|
447
427
|
|
|
448
428
|
# Map team IDs to names
|
|
449
429
|
team_map = {t["id"]: t for t in team_data}
|
|
@@ -495,9 +475,7 @@ def get_double_gameweeks(num_gameweeks: int = 5) -> list[dict[str, Any]]:
|
|
|
495
475
|
return double_gameweeks
|
|
496
476
|
|
|
497
477
|
|
|
498
|
-
def get_player_gameweek_history(
|
|
499
|
-
player_ids: list[int], num_gameweeks: int = 5
|
|
500
|
-
) -> dict[str, Any]:
|
|
478
|
+
def get_player_gameweek_history(player_ids: list[int], num_gameweeks: int = 5) -> dict[str, Any]:
|
|
501
479
|
"""Get recent gameweek history for multiple players.
|
|
502
480
|
|
|
503
481
|
Args:
|
|
@@ -508,9 +486,7 @@ def get_player_gameweek_history(
|
|
|
508
486
|
Dictionary mapping player IDs to their gameweek histories
|
|
509
487
|
"""
|
|
510
488
|
logger = logging.getLogger(__name__)
|
|
511
|
-
logger.info(
|
|
512
|
-
f"Getting gameweek history for {len(player_ids)} players, {num_gameweeks} gameweeks"
|
|
513
|
-
)
|
|
489
|
+
logger.info(f"Getting gameweek history for {len(player_ids)} players, {num_gameweeks} gameweeks")
|
|
514
490
|
|
|
515
491
|
# Get current gameweek to determine range
|
|
516
492
|
gameweeks = api.get_gameweeks()
|
|
@@ -571,28 +547,18 @@ def get_player_gameweek_history(
|
|
|
571
547
|
# Added additional stats as requested
|
|
572
548
|
"expected_goals": entry.get("expected_goals", 0),
|
|
573
549
|
"expected_assists": entry.get("expected_assists", 0),
|
|
574
|
-
"expected_goal_involvements": entry.get(
|
|
575
|
-
|
|
576
|
-
),
|
|
577
|
-
"expected_goals_conceded": entry.get(
|
|
578
|
-
"expected_goals_conceded", 0
|
|
579
|
-
),
|
|
550
|
+
"expected_goal_involvements": entry.get("expected_goal_involvements", 0),
|
|
551
|
+
"expected_goals_conceded": entry.get("expected_goals_conceded", 0),
|
|
580
552
|
"transfers_in": entry.get("transfers_in", 0),
|
|
581
553
|
"transfers_out": entry.get("transfers_out", 0),
|
|
582
554
|
"selected": entry.get("selected", 0),
|
|
583
|
-
"value": entry.get("value", 0) / 10.0
|
|
584
|
-
if "value" in entry
|
|
585
|
-
else 0,
|
|
555
|
+
"value": entry.get("value", 0) / 10.0 if "value" in entry else 0,
|
|
586
556
|
"team_score": entry.get(
|
|
587
|
-
"team_h_score"
|
|
588
|
-
if entry.get("was_home")
|
|
589
|
-
else "team_a_score",
|
|
557
|
+
"team_h_score" if entry.get("was_home") else "team_a_score",
|
|
590
558
|
0,
|
|
591
559
|
),
|
|
592
560
|
"opponent_score": entry.get(
|
|
593
|
-
"team_a_score"
|
|
594
|
-
if entry.get("was_home")
|
|
595
|
-
else "team_h_score",
|
|
561
|
+
"team_a_score" if entry.get("was_home") else "team_h_score",
|
|
596
562
|
0,
|
|
597
563
|
),
|
|
598
564
|
}
|
|
@@ -8,9 +8,7 @@ from .api import api
|
|
|
8
8
|
# Resources
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
def get_players_resource(
|
|
12
|
-
name_filter: str | None = None, team_filter: str | None = None
|
|
13
|
-
) -> list[dict[str, Any]]:
|
|
11
|
+
def get_players_resource(name_filter: str | None = None, team_filter: str | None = None) -> list[dict[str, Any]]:
|
|
14
12
|
"""
|
|
15
13
|
Format player data for the MCP resource.
|
|
16
14
|
|
|
@@ -83,9 +81,7 @@ def get_players_resource(
|
|
|
83
81
|
# Expected stats (if available)
|
|
84
82
|
"expected_goals": player.get("expected_goals", "N/A"),
|
|
85
83
|
"expected_assists": player.get("expected_assists", "N/A"),
|
|
86
|
-
"expected_goal_involvements": player.get(
|
|
87
|
-
"expected_goal_involvements", "N/A"
|
|
88
|
-
),
|
|
84
|
+
"expected_goal_involvements": player.get("expected_goal_involvements", "N/A"),
|
|
89
85
|
"expected_goals_conceded": player.get("expected_goals_conceded", "N/A"),
|
|
90
86
|
# Ownership & transfers
|
|
91
87
|
"selected_by_percent": player["selected_by_percent"],
|
|
@@ -281,17 +277,10 @@ def find_players_by_name(name: str, limit: int = 5) -> list[dict[str, Any]]:
|
|
|
281
277
|
scored_players.append((total_score, player))
|
|
282
278
|
|
|
283
279
|
# Sort by score (highest first)
|
|
284
|
-
sorted_players = [
|
|
285
|
-
player for _, player in sorted(scored_players, key=lambda x: x[0], reverse=True)
|
|
286
|
-
]
|
|
280
|
+
sorted_players = [player for _, player in sorted(scored_players, key=lambda x: x[0], reverse=True)]
|
|
287
281
|
# If no matches with good confidence, fall back to simple contains match
|
|
288
282
|
if not sorted_players or (sorted_players and scored_players[0][0] < 30):
|
|
289
|
-
fallback_players = [
|
|
290
|
-
p
|
|
291
|
-
for p in all_players
|
|
292
|
-
if search_term in p["name"].lower()
|
|
293
|
-
or search_term in p.get("web_name", "").lower()
|
|
294
|
-
]
|
|
283
|
+
fallback_players = [p for p in all_players if search_term in p["name"].lower() or search_term in p.get("web_name", "").lower()]
|
|
295
284
|
# Sort fallback by points
|
|
296
285
|
fallback_players.sort(key=lambda p: float(p["points"]), reverse=True)
|
|
297
286
|
|
|
@@ -338,9 +327,7 @@ def get_current_gameweek_resource() -> dict[str, Any]:
|
|
|
338
327
|
|
|
339
328
|
# Format deadline time to be more readable
|
|
340
329
|
try:
|
|
341
|
-
deadline = datetime.datetime.strptime(
|
|
342
|
-
current_gw["deadline_time"], "%Y-%m-%dT%H:%M:%SZ"
|
|
343
|
-
)
|
|
330
|
+
deadline = datetime.datetime.strptime(current_gw["deadline_time"], "%Y-%m-%dT%H:%M:%SZ")
|
|
344
331
|
gw_data["deadline_formatted"] = deadline.strftime("%A, %d %B %Y at %H:%M UTC")
|
|
345
332
|
|
|
346
333
|
# Calculate time until deadline
|
|
@@ -419,9 +406,7 @@ def get_player_fixtures(player_id: int, num_fixtures: int = 5) -> list[dict[str,
|
|
|
419
406
|
Returns:
|
|
420
407
|
List of upcoming fixtures for the player
|
|
421
408
|
"""
|
|
422
|
-
logger.info(
|
|
423
|
-
f"Getting player fixtures (player_id={player_id}, num_fixtures={num_fixtures})"
|
|
424
|
-
)
|
|
409
|
+
logger.info(f"Getting player fixtures (player_id={player_id}, num_fixtures={num_fixtures})")
|
|
425
410
|
|
|
426
411
|
# Get player data to find their team
|
|
427
412
|
players_data = api.get_players()
|
|
@@ -500,9 +485,7 @@ def get_player_fixtures(player_id: int, num_fixtures: int = 5) -> list[dict[str,
|
|
|
500
485
|
opponent_team = team_map.get(opponent_id, {})
|
|
501
486
|
|
|
502
487
|
# Determine difficulty - higher is more difficult
|
|
503
|
-
difficulty = fixture.get(
|
|
504
|
-
"team_h_difficulty" if is_home else "team_a_difficulty", 3
|
|
505
|
-
)
|
|
488
|
+
difficulty = fixture.get("team_h_difficulty" if is_home else "team_a_difficulty", 3)
|
|
506
489
|
|
|
507
490
|
formatted_fixture = {
|
|
508
491
|
"gameweek": fixture.get("event"),
|
|
@@ -518,9 +501,7 @@ def get_player_fixtures(player_id: int, num_fixtures: int = 5) -> list[dict[str,
|
|
|
518
501
|
return formatted_fixtures
|
|
519
502
|
|
|
520
503
|
|
|
521
|
-
def get_player_gameweek_history(
|
|
522
|
-
player_ids: list[int], num_gameweeks: int = 5
|
|
523
|
-
) -> dict[str, Any]:
|
|
504
|
+
def get_player_gameweek_history(player_ids: list[int], num_gameweeks: int = 5) -> dict[str, Any]:
|
|
524
505
|
"""Get recent gameweek history for multiple players.
|
|
525
506
|
|
|
526
507
|
Args:
|
|
@@ -531,9 +512,7 @@ def get_player_gameweek_history(
|
|
|
531
512
|
Dictionary mapping player IDs to their gameweek histories
|
|
532
513
|
"""
|
|
533
514
|
logger = logging.getLogger(__name__)
|
|
534
|
-
logger.info(
|
|
535
|
-
f"Getting gameweek history for {len(player_ids)} players, {num_gameweeks} gameweeks"
|
|
536
|
-
)
|
|
515
|
+
logger.info(f"Getting gameweek history for {len(player_ids)} players, {num_gameweeks} gameweeks")
|
|
537
516
|
|
|
538
517
|
# Get current gameweek to determine range
|
|
539
518
|
gameweeks = api.get_gameweeks()
|
|
@@ -594,28 +573,18 @@ def get_player_gameweek_history(
|
|
|
594
573
|
# Added additional stats as requested
|
|
595
574
|
"expected_goals": entry.get("expected_goals", 0),
|
|
596
575
|
"expected_assists": entry.get("expected_assists", 0),
|
|
597
|
-
"expected_goal_involvements": entry.get(
|
|
598
|
-
|
|
599
|
-
),
|
|
600
|
-
"expected_goals_conceded": entry.get(
|
|
601
|
-
"expected_goals_conceded", 0
|
|
602
|
-
),
|
|
576
|
+
"expected_goal_involvements": entry.get("expected_goal_involvements", 0),
|
|
577
|
+
"expected_goals_conceded": entry.get("expected_goals_conceded", 0),
|
|
603
578
|
"transfers_in": entry.get("transfers_in", 0),
|
|
604
579
|
"transfers_out": entry.get("transfers_out", 0),
|
|
605
580
|
"selected": entry.get("selected", 0),
|
|
606
|
-
"value": entry.get("value", 0) / 10.0
|
|
607
|
-
if "value" in entry
|
|
608
|
-
else 0,
|
|
581
|
+
"value": entry.get("value", 0) / 10.0 if "value" in entry else 0,
|
|
609
582
|
"team_score": entry.get(
|
|
610
|
-
"team_h_score"
|
|
611
|
-
if entry.get("was_home")
|
|
612
|
-
else "team_a_score",
|
|
583
|
+
"team_h_score" if entry.get("was_home") else "team_a_score",
|
|
613
584
|
0,
|
|
614
585
|
),
|
|
615
586
|
"opponent_score": entry.get(
|
|
616
|
-
"team_a_score"
|
|
617
|
-
if entry.get("was_home")
|
|
618
|
-
else "team_h_score",
|
|
587
|
+
"team_a_score" if entry.get("was_home") else "team_h_score",
|
|
619
588
|
0,
|
|
620
589
|
),
|
|
621
590
|
}
|
|
@@ -738,11 +707,7 @@ def get_player_info(
|
|
|
738
707
|
# Include gameweek history if requested
|
|
739
708
|
if include_history and "history" in player:
|
|
740
709
|
# Filter history by gameweek range
|
|
741
|
-
filtered_history = [
|
|
742
|
-
gw
|
|
743
|
-
for gw in player.get("history", [])
|
|
744
|
-
if start_gameweek <= gw.get("round", 0) <= end_gameweek
|
|
745
|
-
]
|
|
710
|
+
filtered_history = [gw for gw in player.get("history", []) if start_gameweek <= gw.get("round", 0) <= end_gameweek]
|
|
746
711
|
|
|
747
712
|
# Get detailed gameweek history
|
|
748
713
|
player_id_value = player.get("id")
|
|
@@ -765,11 +730,7 @@ def get_player_info(
|
|
|
765
730
|
gw_num = gw_data.get("round")
|
|
766
731
|
# Find matching detailed gameweek
|
|
767
732
|
matching_detailed = next(
|
|
768
|
-
(
|
|
769
|
-
gw
|
|
770
|
-
for gw in detailed_history
|
|
771
|
-
if gw.get("round") == gw_num or gw.get("gameweek") == gw_num
|
|
772
|
-
),
|
|
733
|
+
(gw for gw in detailed_history if gw.get("round") == gw_num or gw.get("gameweek") == gw_num),
|
|
773
734
|
None,
|
|
774
735
|
)
|
|
775
736
|
|
|
@@ -825,11 +786,7 @@ def get_player_info(
|
|
|
825
786
|
|
|
826
787
|
# Calculate average fixture difficulty
|
|
827
788
|
difficulty_values = [f.get("difficulty", 3) for f in fixtures_data]
|
|
828
|
-
avg_difficulty = (
|
|
829
|
-
sum(difficulty_values) / len(difficulty_values)
|
|
830
|
-
if difficulty_values
|
|
831
|
-
else 3
|
|
832
|
-
)
|
|
789
|
+
avg_difficulty = sum(difficulty_values) / len(difficulty_values) if difficulty_values else 3
|
|
833
790
|
|
|
834
791
|
# Convert to a 1-10 scale where 10 is best (easiest fixtures)
|
|
835
792
|
fixture_score = (6 - avg_difficulty) * 2
|
|
@@ -837,18 +794,12 @@ def get_player_info(
|
|
|
837
794
|
result["fixture_analysis"] = {
|
|
838
795
|
"difficulty_score": round(fixture_score, 1),
|
|
839
796
|
"fixtures_analyzed": len(fixtures_data),
|
|
840
|
-
"home_matches": sum(
|
|
841
|
-
|
|
842
|
-
),
|
|
843
|
-
"away_matches": sum(
|
|
844
|
-
1 for f in fixtures_data if f.get("location") == "away"
|
|
845
|
-
),
|
|
797
|
+
"home_matches": sum(1 for f in fixtures_data if f.get("location") == "home"),
|
|
798
|
+
"away_matches": sum(1 for f in fixtures_data if f.get("location") == "away"),
|
|
846
799
|
}
|
|
847
800
|
|
|
848
801
|
# Add fixture difficulty assessment
|
|
849
|
-
if "fixture_analysis" in result and isinstance(
|
|
850
|
-
result["fixture_analysis"], dict
|
|
851
|
-
):
|
|
802
|
+
if "fixture_analysis" in result and isinstance(result["fixture_analysis"], dict):
|
|
852
803
|
fixture_analysis = result["fixture_analysis"]
|
|
853
804
|
if fixture_score >= 8:
|
|
854
805
|
fixture_analysis["assessment"] = "Excellent fixtures"
|
|
@@ -862,9 +813,7 @@ def get_player_info(
|
|
|
862
813
|
return result
|
|
863
814
|
|
|
864
815
|
|
|
865
|
-
def search_players(
|
|
866
|
-
query: str, position: str | None = None, team: str | None = None, limit: int = 5
|
|
867
|
-
) -> dict[str, Any]:
|
|
816
|
+
def search_players(query: str, position: str | None = None, team: str | None = None, limit: int = 5) -> dict[str, Any]:
|
|
868
817
|
"""
|
|
869
818
|
Search for players by name with optional filtering by position and team.
|
|
870
819
|
|
|
@@ -881,9 +830,7 @@ def search_players(
|
|
|
881
830
|
logger.info(f"Searching players: query={query}, position={position}, team={team}")
|
|
882
831
|
|
|
883
832
|
# Find players by name
|
|
884
|
-
matches = find_players_by_name(
|
|
885
|
-
query, limit=limit * 2
|
|
886
|
-
) # Get more than needed for filtering
|
|
833
|
+
matches = find_players_by_name(query, limit=limit * 2) # Get more than needed for filtering
|
|
887
834
|
|
|
888
835
|
# Apply position filter if specified
|
|
889
836
|
if position and matches:
|
|
@@ -891,12 +838,7 @@ def search_players(
|
|
|
891
838
|
|
|
892
839
|
# Apply team filter if specified
|
|
893
840
|
if team and matches:
|
|
894
|
-
matches = [
|
|
895
|
-
p
|
|
896
|
-
for p in matches
|
|
897
|
-
if team.lower() in p.get("team", "").lower()
|
|
898
|
-
or team.lower() in p.get("team_short", "").lower()
|
|
899
|
-
]
|
|
841
|
+
matches = [p for p in matches if team.lower() in p.get("team", "").lower() or team.lower() in p.get("team_short", "").lower()]
|
|
900
842
|
|
|
901
843
|
# Limit results
|
|
902
844
|
matches = matches[:limit]
|
|
@@ -965,18 +907,12 @@ def get_team_by_name(name: str) -> dict[str, Any] | None:
|
|
|
965
907
|
|
|
966
908
|
# Try exact match first
|
|
967
909
|
for team in teams:
|
|
968
|
-
if (
|
|
969
|
-
team["name"].lower() == name_lower
|
|
970
|
-
or team["short_name"].lower() == name_lower
|
|
971
|
-
):
|
|
910
|
+
if team["name"].lower() == name_lower or team["short_name"].lower() == name_lower:
|
|
972
911
|
return team
|
|
973
912
|
|
|
974
913
|
# Then try partial match
|
|
975
914
|
for team in teams:
|
|
976
|
-
if (
|
|
977
|
-
name_lower in team["name"].lower()
|
|
978
|
-
or name_lower in team["short_name"].lower()
|
|
979
|
-
):
|
|
915
|
+
if name_lower in team["name"].lower() or name_lower in team["short_name"].lower():
|
|
980
916
|
return team
|
|
981
917
|
|
|
982
918
|
return None
|
|
@@ -20,12 +20,8 @@ def parse_league_standings(data: dict[str, Any]) -> dict[str, Any]:
|
|
|
20
20
|
"id": data.get("league", {}).get("id"),
|
|
21
21
|
"name": data.get("league", {}).get("name"),
|
|
22
22
|
"created": data.get("league", {}).get("created"),
|
|
23
|
-
"type": "Public"
|
|
24
|
-
if data.get("league", {}).get("
|
|
25
|
-
else "Private",
|
|
26
|
-
"scoring": "Classic"
|
|
27
|
-
if data.get("league", {}).get("scoring") == "c"
|
|
28
|
-
else "Head-to-Head",
|
|
23
|
+
"type": "Public" if data.get("league", {}).get("league_type") == "s" else "Private",
|
|
24
|
+
"scoring": "Classic" if data.get("league", {}).get("scoring") == "c" else "Head-to-Head",
|
|
29
25
|
"admin_entry": data.get("league", {}).get("admin_entry"),
|
|
30
26
|
"start_event": data.get("league", {}).get("start_event"),
|
|
31
27
|
}
|
|
@@ -66,9 +62,7 @@ def parse_league_standings(data: dict[str, Any]) -> dict[str, Any]:
|
|
|
66
62
|
return response
|
|
67
63
|
|
|
68
64
|
|
|
69
|
-
def get_teams_historical_data(
|
|
70
|
-
team_ids: list[int], api, start_gw: int | None = None, end_gw: int | None = None
|
|
71
|
-
) -> dict[str, Any]:
|
|
65
|
+
def get_teams_historical_data(team_ids: list[int], api, start_gw: int | None = None, end_gw: int | None = None) -> dict[str, Any]:
|
|
72
66
|
"""Get historical data for multiple teams"""
|
|
73
67
|
results = {}
|
|
74
68
|
errors = {}
|
|
@@ -114,11 +108,7 @@ def get_teams_historical_data(
|
|
|
114
108
|
history_data = api._make_request(f"entry/{team_id}/history/")
|
|
115
109
|
|
|
116
110
|
if "current" in history_data:
|
|
117
|
-
current = [
|
|
118
|
-
gw
|
|
119
|
-
for gw in history_data["current"]
|
|
120
|
-
if start_gw <= gw.get("event", 0) <= end_gw
|
|
121
|
-
]
|
|
111
|
+
current = [gw for gw in history_data["current"] if start_gw <= gw.get("event", 0) <= end_gw]
|
|
122
112
|
|
|
123
113
|
filtered_data = {
|
|
124
114
|
"current": current,
|
|
@@ -158,12 +148,8 @@ def _get_league_standings(league_id: int, api) -> dict[str, Any]:
|
|
|
158
148
|
"id": data.get("league", {}).get("id"),
|
|
159
149
|
"name": data.get("league", {}).get("name"),
|
|
160
150
|
"created": data.get("league", {}).get("created"),
|
|
161
|
-
"type": "Public"
|
|
162
|
-
if data.get("league", {}).get("
|
|
163
|
-
else "Private",
|
|
164
|
-
"scoring": "Classic"
|
|
165
|
-
if data.get("league", {}).get("scoring") == "c"
|
|
166
|
-
else "Head-to-Head",
|
|
151
|
+
"type": "Public" if data.get("league", {}).get("league_type") == "s" else "Private",
|
|
152
|
+
"scoring": "Classic" if data.get("league", {}).get("scoring") == "c" else "Head-to-Head",
|
|
167
153
|
"admin_entry": data.get("league", {}).get("admin_entry"),
|
|
168
154
|
"start_event": data.get("league", {}).get("start_event"),
|
|
169
155
|
}
|
|
@@ -247,9 +233,7 @@ def _get_league_historical_performance(
|
|
|
247
233
|
if gw_data:
|
|
248
234
|
points_series.append(gw_data.get("points", 0))
|
|
249
235
|
rank_series.append(gw_data.get("overall_rank", 0))
|
|
250
|
-
value_series.append(
|
|
251
|
-
gw_data.get("value", 0) / 10.0 if gw_data.get("value") else 0
|
|
252
|
-
)
|
|
236
|
+
value_series.append(gw_data.get("value", 0) / 10.0 if gw_data.get("value") else 0)
|
|
253
237
|
else:
|
|
254
238
|
points_series.append(0)
|
|
255
239
|
rank_series.append(0)
|
|
@@ -292,9 +276,7 @@ def _get_league_historical_performance(
|
|
|
292
276
|
|
|
293
277
|
if valid_ranks:
|
|
294
278
|
mean_rank = sum(valid_ranks) / len(valid_ranks)
|
|
295
|
-
rank_variance = sum((r - mean_rank) ** 2 for r in valid_ranks) / len(
|
|
296
|
-
valid_ranks
|
|
297
|
-
)
|
|
279
|
+
rank_variance = sum((r - mean_rank) ** 2 for r in valid_ranks) / len(valid_ranks)
|
|
298
280
|
consistency_score = 10.0 - min(10.0, (rank_variance / 1000000) * 10)
|
|
299
281
|
team["consistency_score"] = round(consistency_score, 1)
|
|
300
282
|
else:
|
|
@@ -381,9 +363,7 @@ def _get_league_team_composition(
|
|
|
381
363
|
team_values = {}
|
|
382
364
|
|
|
383
365
|
for team_id, team_data in teams_data.items():
|
|
384
|
-
team_info = next(
|
|
385
|
-
(t for t in league_data["standings"] if t["team_id"] == team_id), None
|
|
386
|
-
)
|
|
366
|
+
team_info = next((t for t in league_data["standings"] if t["team_id"] == team_id), None)
|
|
387
367
|
if not team_info:
|
|
388
368
|
continue
|
|
389
369
|
|
|
@@ -415,16 +395,10 @@ def _get_league_team_composition(
|
|
|
415
395
|
"full_name": full_name.strip() or "Unknown",
|
|
416
396
|
"position": position,
|
|
417
397
|
"team": player_data.get("team_short", "UNK"),
|
|
418
|
-
"price": player_data.get("now_cost", 0) / 10.0
|
|
419
|
-
if player_data.get("
|
|
420
|
-
else 0,
|
|
421
|
-
"form": float(player_data.get("form", "0.0"))
|
|
422
|
-
if player_data.get("form")
|
|
423
|
-
else 0,
|
|
398
|
+
"price": player_data.get("now_cost", 0) / 10.0 if player_data.get("now_cost") else 0,
|
|
399
|
+
"form": float(player_data.get("form", "0.0")) if player_data.get("form") else 0,
|
|
424
400
|
"total_points": player_data.get("total_points", 0),
|
|
425
|
-
"points_per_game": float(player_data.get("points_per_game", "0.0"))
|
|
426
|
-
if player_data.get("points_per_game")
|
|
427
|
-
else 0,
|
|
401
|
+
"points_per_game": float(player_data.get("points_per_game", "0.0")) if player_data.get("points_per_game") else 0,
|
|
428
402
|
"ownership_count": 0,
|
|
429
403
|
"ownership_percent": 0,
|
|
430
404
|
"captain_count": 0,
|
|
@@ -465,9 +439,7 @@ def _get_league_team_composition(
|
|
|
465
439
|
|
|
466
440
|
team_count = len(teams_data)
|
|
467
441
|
for player_id, player in player_ownership.items():
|
|
468
|
-
player["ownership_percent"] = round(
|
|
469
|
-
(player["ownership_count"] / team_count) * 100, 1
|
|
470
|
-
)
|
|
442
|
+
player["ownership_percent"] = round((player["ownership_count"] / team_count) * 100, 1)
|
|
471
443
|
|
|
472
444
|
players_by_ownership = sorted(
|
|
473
445
|
player_ownership.values(),
|
|
@@ -478,27 +450,15 @@ def _get_league_team_composition(
|
|
|
478
450
|
template_threshold = 30.0
|
|
479
451
|
differential_threshold = 10.0
|
|
480
452
|
|
|
481
|
-
template_players = [
|
|
482
|
-
|
|
483
|
-
]
|
|
484
|
-
differential_players = [
|
|
485
|
-
p
|
|
486
|
-
for p in players_by_ownership
|
|
487
|
-
if 0 < p["ownership_percent"] < differential_threshold
|
|
488
|
-
]
|
|
453
|
+
template_players = [p for p in players_by_ownership if p["ownership_percent"] > template_threshold]
|
|
454
|
+
differential_players = [p for p in players_by_ownership if 0 < p["ownership_percent"] < differential_threshold]
|
|
489
455
|
|
|
490
456
|
position_order = {"GKP": 1, "DEF": 2, "MID": 3, "FWD": 4, "UNK": 5}
|
|
491
|
-
template_players.sort(
|
|
492
|
-
|
|
493
|
-
)
|
|
494
|
-
differential_players.sort(
|
|
495
|
-
key=lambda p: (position_order.get(p["position"], 5), -p["total_points"])
|
|
496
|
-
)
|
|
457
|
+
template_players.sort(key=lambda p: (position_order.get(p["position"], 5), -p["ownership_percent"]))
|
|
458
|
+
differential_players.sort(key=lambda p: (position_order.get(p["position"], 5), -p["total_points"]))
|
|
497
459
|
|
|
498
460
|
captain_data = []
|
|
499
|
-
for player_id, count in sorted(
|
|
500
|
-
captain_picks.items(), key=lambda x: x[1], reverse=True
|
|
501
|
-
):
|
|
461
|
+
for player_id, count in sorted(captain_picks.items(), key=lambda x: x[1], reverse=True):
|
|
502
462
|
if player_id in player_ownership:
|
|
503
463
|
player = player_ownership[player_id]
|
|
504
464
|
captain_data.append(
|
|
@@ -522,9 +482,7 @@ def _get_league_team_composition(
|
|
|
522
482
|
"team_value": value_data["value"],
|
|
523
483
|
}
|
|
524
484
|
|
|
525
|
-
sorted_value_stats = sorted(
|
|
526
|
-
value_stats.values(), key=lambda v: v["team_value"], reverse=True
|
|
527
|
-
)
|
|
485
|
+
sorted_value_stats = sorted(value_stats.values(), key=lambda v: v["team_value"], reverse=True)
|
|
528
486
|
|
|
529
487
|
PLAYER_LIMIT = 25
|
|
530
488
|
|
|
@@ -536,9 +494,7 @@ def _get_league_team_composition(
|
|
|
536
494
|
"all_players": players_by_ownership[:PLAYER_LIMIT],
|
|
537
495
|
"template_players": template_players[:PLAYER_LIMIT],
|
|
538
496
|
"differential_players": differential_players[:PLAYER_LIMIT],
|
|
539
|
-
"captain_picks": captain_data[:PLAYER_LIMIT]
|
|
540
|
-
if len(captain_data) > PLAYER_LIMIT
|
|
541
|
-
else captain_data,
|
|
497
|
+
"captain_picks": captain_data[:PLAYER_LIMIT] if len(captain_data) > PLAYER_LIMIT else captain_data,
|
|
542
498
|
},
|
|
543
499
|
"team_values": sorted_value_stats,
|
|
544
500
|
"errors": errors,
|