cito-mcp 0.5.8 → 0.6.1
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/README.md +21 -21
- package/dist/client.js +8 -8
- package/dist/envelope.js +11 -11
- package/dist/index.js +2 -2
- package/dist/install.js +15 -15
- package/dist/instructions.js +10 -4
- package/dist/pacing.js +2 -2
- package/dist/scrub.js +87 -11
- package/dist/tools/combat.js +96 -96
- package/dist/tools/index.js +2 -0
- package/dist/tools/insight.js +48 -46
- package/dist/tools/leaderboard.js +6 -6
- package/dist/tools/live.js +45 -44
- package/dist/tools/match.js +90 -43
- package/dist/tools/meta.js +108 -73
- package/dist/tools/normalize.js +3 -2
- package/dist/tools/player.js +67 -28
- package/dist/tools/rankings.js +42 -9
- package/dist/tools/resolve.js +61 -61
- package/dist/tools/schedule.js +15 -14
- package/dist/tools/standings.js +66 -46
- package/dist/tools/team.js +91 -89
- package/dist/tools/tournaments.js +7 -9
- package/dist/tools/types.js +1 -1
- package/dist/tools/webhooks.js +377 -0
- package/package.json +51 -52
package/dist/tools/normalize.js
CHANGED
|
@@ -10,7 +10,8 @@ function deriveProxiedImageUrl(rawUrl, game) {
|
|
|
10
10
|
catch {
|
|
11
11
|
return null;
|
|
12
12
|
}
|
|
13
|
-
|
|
13
|
+
const fighterImageHost = 'ufc' + '.com';
|
|
14
|
+
if (host !== fighterImageHost && !host.endsWith(`.${fighterImageHost}`))
|
|
14
15
|
return null;
|
|
15
16
|
return `${IMAGE_PROXY_BASE}/public/images/ufc/${Buffer.from(rawUrl, 'utf8').toString('base64url')}`;
|
|
16
17
|
}
|
|
@@ -558,7 +559,7 @@ export function normalizeMatch(game, row, forcedStatus) {
|
|
|
558
559
|
? { sets: tennisSets, gameScore: tennisGameScore, server: tennisServer, currentSet: tennisCurrentSet }
|
|
559
560
|
: {}),
|
|
560
561
|
...(game === 'tennis' && typeof r.is_simulated === 'boolean'
|
|
561
|
-
? { isSimulated: r.is_simulated
|
|
562
|
+
? { isSimulated: r.is_simulated }
|
|
562
563
|
: {}),
|
|
563
564
|
...(isCancelled
|
|
564
565
|
? { cancelled: { isCancelled: true, reason: pickString(r.cancellationReason) ?? null } }
|
package/dist/tools/player.js
CHANGED
|
@@ -40,6 +40,25 @@ function identityFrom(game, raw, idHint, slugHint) {
|
|
|
40
40
|
attribution: pickString(r.portrait_attribution) ?? null,
|
|
41
41
|
sourceUrl: pickString(r.portrait_source_url) ?? null,
|
|
42
42
|
},
|
|
43
|
+
...(game === 'tennis' ? { tennis: tennisIdentity(r) } : {}),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function tennisIdentity(r) {
|
|
47
|
+
const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : null);
|
|
48
|
+
return {
|
|
49
|
+
tour: pickString(r.tour) ?? null,
|
|
50
|
+
currentRank: num(r.current_rank),
|
|
51
|
+
currentRankPoints: num(r.current_rank_points),
|
|
52
|
+
lastRank: num(r.last_rank),
|
|
53
|
+
lastRankDate: pickString(r.last_rank_date) ?? null,
|
|
54
|
+
careerHighRank: num(r.career_high_rank),
|
|
55
|
+
status: pickString(r.status) ?? null,
|
|
56
|
+
statusBasis: pickString(r.status_basis) ?? null,
|
|
57
|
+
retiredYear: num(r.retired_year),
|
|
58
|
+
lastMatchDate: pickString(r.last_match_date) ?? null,
|
|
59
|
+
isActive: typeof r.is_active === 'boolean' ? r.is_active : null,
|
|
60
|
+
hand: pickString(r.hand) ?? null,
|
|
61
|
+
isoAlpha2: pickString(r.iso_alpha2) ?? null,
|
|
43
62
|
};
|
|
44
63
|
}
|
|
45
64
|
export const playerProfile = {
|
|
@@ -54,7 +73,9 @@ Prefer over: manual multi-call career/trends/matches via call_api.
|
|
|
54
73
|
|
|
55
74
|
Do not use when: full team roster needed → team_profile; unresolved name → resolve_entity first.
|
|
56
75
|
|
|
57
|
-
|
|
76
|
+
Tennis: player.tennis.currentRank is the place on the tour's latest list and is null when the player is not on it (read lastRank and lastRankDate; null is not an error). player.tennis.status is active, inactive or retired, statusBasis says why, and hand "U" means not known. career.career_summary and career.surface_breakdown count singles only; career_summary.doubles and career_summary.combined hold the other scopes.
|
|
77
|
+
|
|
78
|
+
Parallel-safe: yes. API calls: 2–5.
|
|
58
79
|
Example: { "game": "cs2", "playerId": "cs2-player-1", "recentLimit": 10, "includeTrends": true }`,
|
|
59
80
|
inputSchema: {
|
|
60
81
|
type: 'object',
|
|
@@ -126,7 +147,7 @@ Example: { "game": "cs2", "playerId": "cs2-player-1", "recentLimit": 10, "includ
|
|
|
126
147
|
const includeTrends = args.includeTrends !== false;
|
|
127
148
|
const view = args.view === 'full' ? 'full' : args.view === 'compact' ? 'compact' : 'summary';
|
|
128
149
|
const partial = [];
|
|
129
|
-
let
|
|
150
|
+
let apiCalls = 0;
|
|
130
151
|
let rateLimit = {};
|
|
131
152
|
let primaryPath = '';
|
|
132
153
|
if (game === 'lol')
|
|
@@ -144,7 +165,7 @@ Example: { "game": "cs2", "playerId": "cs2-player-1", "recentLimit": 10, "includ
|
|
|
144
165
|
let playerRaw = null;
|
|
145
166
|
if (game === 'dota2') {
|
|
146
167
|
const list = await fetchJson(ctx, '/dota2/players', { query: { search: idOrSlug, limit: 5 } });
|
|
147
|
-
|
|
168
|
+
apiCalls += 1;
|
|
148
169
|
rateLimit = list.headers;
|
|
149
170
|
if (list.ok) {
|
|
150
171
|
const rows = extractRows(list.data);
|
|
@@ -161,7 +182,7 @@ Example: { "game": "cs2", "playerId": "cs2-player-1", "recentLimit": 10, "includ
|
|
|
161
182
|
}
|
|
162
183
|
else {
|
|
163
184
|
const res = await fetchJson(ctx, primaryPath);
|
|
164
|
-
|
|
185
|
+
apiCalls += 1;
|
|
165
186
|
rateLimit = res.headers;
|
|
166
187
|
if (!res.ok) {
|
|
167
188
|
return errorEnvelope({
|
|
@@ -171,7 +192,7 @@ Example: { "game": "cs2", "playerId": "cs2-player-1", "recentLimit": 10, "includ
|
|
|
171
192
|
source: 'player_profile',
|
|
172
193
|
requestId,
|
|
173
194
|
tookMs: Date.now() - started,
|
|
174
|
-
|
|
195
|
+
apiCalls,
|
|
175
196
|
rateLimit: res.headers,
|
|
176
197
|
httpStatus: res.status,
|
|
177
198
|
});
|
|
@@ -189,7 +210,7 @@ Example: { "game": "cs2", "playerId": "cs2-player-1", "recentLimit": 10, "includ
|
|
|
189
210
|
if (game === 'lol') {
|
|
190
211
|
tasks.push((async () => {
|
|
191
212
|
const res = await fetchJson(ctx, `/lol/players/${encodeURIComponent(idOrSlug)}/teams`);
|
|
192
|
-
|
|
213
|
+
apiCalls += 1;
|
|
193
214
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
194
215
|
if (res.ok) {
|
|
195
216
|
const rows = extractRows(res.data);
|
|
@@ -227,7 +248,7 @@ Example: { "game": "cs2", "playerId": "cs2-player-1", "recentLimit": 10, "includ
|
|
|
227
248
|
const res = await fetchJson(ctx, `/lol/players/${encodeURIComponent(idOrSlug)}/matches`, {
|
|
228
249
|
query: { limit: String(recentLimit) },
|
|
229
250
|
});
|
|
230
|
-
|
|
251
|
+
apiCalls += 1;
|
|
231
252
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
232
253
|
if (res.ok) {
|
|
233
254
|
const startMs = (row) => {
|
|
@@ -248,7 +269,7 @@ Example: { "game": "cs2", "playerId": "cs2-player-1", "recentLimit": 10, "includ
|
|
|
248
269
|
const names = new Map();
|
|
249
270
|
await Promise.all(slugs.map(async (sl) => {
|
|
250
271
|
const t = await fetchJson(ctx, `/lol/teams/${encodeURIComponent(sl)}`);
|
|
251
|
-
|
|
272
|
+
apiCalls += 1;
|
|
252
273
|
if (!t.ok)
|
|
253
274
|
return;
|
|
254
275
|
const body = asRecord(t.data) ?? {};
|
|
@@ -280,7 +301,7 @@ Example: { "game": "cs2", "playerId": "cs2-player-1", "recentLimit": 10, "includ
|
|
|
280
301
|
if (includeTrends) {
|
|
281
302
|
tasks.push((async () => {
|
|
282
303
|
const res = await fetchJson(ctx, `/tennis/players/${encodeURIComponent(idOrSlug)}/stats`);
|
|
283
|
-
|
|
304
|
+
apiCalls += 1;
|
|
284
305
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
285
306
|
if (res.ok)
|
|
286
307
|
career = unwrapPayload(res.data);
|
|
@@ -297,7 +318,7 @@ Example: { "game": "cs2", "playerId": "cs2-player-1", "recentLimit": 10, "includ
|
|
|
297
318
|
const res = await fetchJson(ctx, `/tennis/players/${encodeURIComponent(idOrSlug)}/matches`, {
|
|
298
319
|
query: { page_size: recentLimit, ...(view === 'compact' ? { view: 'summary' } : {}) },
|
|
299
320
|
});
|
|
300
|
-
|
|
321
|
+
apiCalls += 1;
|
|
301
322
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
302
323
|
if (res.ok) {
|
|
303
324
|
const envelope = asRecord(res.data) ?? {};
|
|
@@ -321,7 +342,7 @@ Example: { "game": "cs2", "playerId": "cs2-player-1", "recentLimit": 10, "includ
|
|
|
321
342
|
]) {
|
|
322
343
|
tasks.push((async () => {
|
|
323
344
|
const res = await fetchJson(ctx, path);
|
|
324
|
-
|
|
345
|
+
apiCalls += 1;
|
|
325
346
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
326
347
|
if (!res.ok) {
|
|
327
348
|
partial.push(partialFromRejection(section, {
|
|
@@ -356,7 +377,7 @@ Example: { "game": "cs2", "playerId": "cs2-player-1", "recentLimit": 10, "includ
|
|
|
356
377
|
const res = await fetchJson(ctx, `/dota2/players/${encodeURIComponent(idOrSlug)}/radar`, {
|
|
357
378
|
query,
|
|
358
379
|
});
|
|
359
|
-
|
|
380
|
+
apiCalls += 1;
|
|
360
381
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
361
382
|
if (res.ok) {
|
|
362
383
|
radar = res.data;
|
|
@@ -379,7 +400,7 @@ Example: { "game": "cs2", "playerId": "cs2-player-1", "recentLimit": 10, "includ
|
|
|
379
400
|
if (game === 'cod') {
|
|
380
401
|
tasks.push((async () => {
|
|
381
402
|
const res = await fetchJson(ctx, `/cod/players/${encodeURIComponent(idOrSlug)}/stats`);
|
|
382
|
-
|
|
403
|
+
apiCalls += 1;
|
|
383
404
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
384
405
|
if (res.ok) {
|
|
385
406
|
seasonStats = res.data;
|
|
@@ -397,7 +418,7 @@ Example: { "game": "cs2", "playerId": "cs2-player-1", "recentLimit": 10, "includ
|
|
|
397
418
|
const res = await fetchJson(ctx, `/cod/players/${encodeURIComponent(idOrSlug)}/matches`, {
|
|
398
419
|
query: { limit: recentLimit },
|
|
399
420
|
});
|
|
400
|
-
|
|
421
|
+
apiCalls += 1;
|
|
401
422
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
402
423
|
if (res.ok) {
|
|
403
424
|
recentMatches = extractRows(res.data)
|
|
@@ -417,7 +438,7 @@ Example: { "game": "cs2", "playerId": "cs2-player-1", "recentLimit": 10, "includ
|
|
|
417
438
|
const fighterKey = slug || idOrSlug;
|
|
418
439
|
tasks.push((async () => {
|
|
419
440
|
const res = await fetchJson(ctx, `/ufc/fighters/${encodeURIComponent(fighterKey)}/stats`);
|
|
420
|
-
|
|
441
|
+
apiCalls += 1;
|
|
421
442
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
422
443
|
if (res.ok) {
|
|
423
444
|
const metrics = unwrapPayload(res.data);
|
|
@@ -435,7 +456,7 @@ Example: { "game": "cs2", "playerId": "cs2-player-1", "recentLimit": 10, "includ
|
|
|
435
456
|
const res = await fetchJson(ctx, `/ufc/fighters/${encodeURIComponent(fighterKey)}/fights`, {
|
|
436
457
|
query: { limit: recentLimit },
|
|
437
458
|
});
|
|
438
|
-
|
|
459
|
+
apiCalls += 1;
|
|
439
460
|
rateLimit = { ...rateLimit, ...res.headers };
|
|
440
461
|
if (res.ok) {
|
|
441
462
|
recentMatches = extractRows(res.data)
|
|
@@ -456,7 +477,7 @@ Example: { "game": "cs2", "playerId": "cs2-player-1", "recentLimit": 10, "includ
|
|
|
456
477
|
const hist = await fetchJson(ctx, `/ufc/fighters/${encodeURIComponent(fighterKey)}/history`, {
|
|
457
478
|
query: { limit: recentLimit },
|
|
458
479
|
});
|
|
459
|
-
|
|
480
|
+
apiCalls += 1;
|
|
460
481
|
rateLimit = { ...rateLimit, ...hist.headers };
|
|
461
482
|
if (hist.ok) {
|
|
462
483
|
recentMatches = extractRows(hist.data)
|
|
@@ -486,7 +507,7 @@ Example: { "game": "cs2", "playerId": "cs2-player-1", "recentLimit": 10, "includ
|
|
|
486
507
|
game,
|
|
487
508
|
requestId,
|
|
488
509
|
tookMs: Date.now() - started,
|
|
489
|
-
|
|
510
|
+
apiCalls,
|
|
490
511
|
rateLimit,
|
|
491
512
|
partial: partial.length ? partial : undefined,
|
|
492
513
|
entities: {
|
|
@@ -551,9 +572,9 @@ Prefer over: player_profile (identity + career aggregates, no streak); raw form
|
|
|
551
572
|
|
|
552
573
|
Do not use when: career totals/titles → player_profile with game tennis (wires /stats); ranking deltas → rankings_movers.
|
|
553
574
|
|
|
554
|
-
Tennis-only. Limit defaults to 10 (max 50, matching the API).
|
|
575
|
+
Tennis-only. Limit defaults to 10 (max 50, matching the API). Rows arrive newest-first; the streak is the leading run of that order.
|
|
555
576
|
|
|
556
|
-
Parallel-safe: yes.
|
|
577
|
+
Parallel-safe: yes. API calls: 1.`,
|
|
557
578
|
inputSchema: {
|
|
558
579
|
type: 'object',
|
|
559
580
|
additionalProperties: false,
|
|
@@ -635,7 +656,7 @@ Parallel-safe: yes. Upstream cost: 1.`,
|
|
|
635
656
|
source: 'player_form',
|
|
636
657
|
requestId,
|
|
637
658
|
tookMs: Date.now() - started,
|
|
638
|
-
|
|
659
|
+
apiCalls: 1,
|
|
639
660
|
httpStatus: res.status,
|
|
640
661
|
rateLimit: res.headers,
|
|
641
662
|
});
|
|
@@ -674,7 +695,7 @@ Parallel-safe: yes. Upstream cost: 1.`,
|
|
|
674
695
|
game,
|
|
675
696
|
requestId,
|
|
676
697
|
tookMs: Date.now() - started,
|
|
677
|
-
|
|
698
|
+
apiCalls: 1,
|
|
678
699
|
rateLimit: res.headers,
|
|
679
700
|
entities: {
|
|
680
701
|
games: [game],
|
|
@@ -713,7 +734,7 @@ Do not use when: aggregate totals, titles, or win% → player_stats; identity
|
|
|
713
734
|
Tennis-only. Limit defaults to 20 (max 50). Rows arrive newest-first. Use
|
|
714
735
|
surface to narrow to Hard/Clay/Grass; tournamentId pins one event.
|
|
715
736
|
|
|
716
|
-
Parallel-safe: yes.
|
|
737
|
+
Parallel-safe: yes. API calls: 1.`,
|
|
717
738
|
inputSchema: {
|
|
718
739
|
type: 'object',
|
|
719
740
|
additionalProperties: false,
|
|
@@ -784,7 +805,7 @@ Parallel-safe: yes. Upstream cost: 1.`,
|
|
|
784
805
|
source: 'player_matches',
|
|
785
806
|
requestId,
|
|
786
807
|
tookMs: Date.now() - started,
|
|
787
|
-
|
|
808
|
+
apiCalls: 1,
|
|
788
809
|
httpStatus: res.status,
|
|
789
810
|
rateLimit: res.headers,
|
|
790
811
|
recover: ['Check the player id with search_entities'],
|
|
@@ -852,7 +873,7 @@ Parallel-safe: yes. Upstream cost: 1.`,
|
|
|
852
873
|
game,
|
|
853
874
|
requestId,
|
|
854
875
|
tookMs: Date.now() - started,
|
|
855
|
-
|
|
876
|
+
apiCalls: 1,
|
|
856
877
|
rateLimit: res.headers,
|
|
857
878
|
entities: { games: [game], ids: { playerId } },
|
|
858
879
|
data: {
|
|
@@ -880,7 +901,9 @@ Prefer over: player_profile (identity + a summary block, no breakdowns); player_
|
|
|
880
901
|
Do not use when: the match log → player_matches; ranking over time → player_rankings_history.
|
|
881
902
|
|
|
882
903
|
Tennis-only. Filters are surface (Hard/Clay/Grass/Carpet) and yearFrom/yearTo.
|
|
883
|
-
|
|
904
|
+
|
|
905
|
+
careerSummary and bySurface count singles only, at every level held (Challenger and ITF, walkovers and qualifying included), so they read higher than tour-level career records. careerSummary.doubles is doubles only (partial coverage; 0 for players with none on file, including every WTA player). careerSummary.combined is singles plus doubles where the player is listed first. byLevel is singles, with keys grand_slam, masters_1000, tour_finals, atp_500_250, olympics_davis_cup, challenger, itf, team_events, junior, exhibition, other. prizeMoneyUsd is the career total where known, null when a filter is set.
|
|
906
|
+
API calls: 1.`,
|
|
884
907
|
inputSchema: {
|
|
885
908
|
type: 'object',
|
|
886
909
|
additionalProperties: false,
|
|
@@ -959,7 +982,7 @@ Upstream cost: 1.`,
|
|
|
959
982
|
source: 'player_stats',
|
|
960
983
|
requestId,
|
|
961
984
|
tookMs: Date.now() - started,
|
|
962
|
-
|
|
985
|
+
apiCalls: 1,
|
|
963
986
|
httpStatus: res.status,
|
|
964
987
|
rateLimit: res.headers,
|
|
965
988
|
recover: ['Check the player id with search_entities'],
|
|
@@ -970,6 +993,19 @@ Upstream cost: 1.`,
|
|
|
970
993
|
const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : null);
|
|
971
994
|
const summary = asRecord(body.career_summary) ?? {};
|
|
972
995
|
const filters = asRecord(body.filters) ?? {};
|
|
996
|
+
const careerScope = (raw) => {
|
|
997
|
+
const s = asRecord(raw);
|
|
998
|
+
if (!s)
|
|
999
|
+
return null;
|
|
1000
|
+
return {
|
|
1001
|
+
matchesPlayed: num(s.matches_played),
|
|
1002
|
+
matchesWon: num(s.matches_won),
|
|
1003
|
+
matchesLost: num(s.matches_lost),
|
|
1004
|
+
winPercentage: num(s.win_percentage),
|
|
1005
|
+
titles: num(s.titles_count),
|
|
1006
|
+
grandSlamTitles: num(s.grand_slam_titles),
|
|
1007
|
+
};
|
|
1008
|
+
};
|
|
973
1009
|
const camel = (key) => key.replace(/_([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
974
1010
|
const flatNumeric = (value) => {
|
|
975
1011
|
const rec = asRecord(value);
|
|
@@ -1035,7 +1071,7 @@ Upstream cost: 1.`,
|
|
|
1035
1071
|
game,
|
|
1036
1072
|
requestId,
|
|
1037
1073
|
tookMs: Date.now() - started,
|
|
1038
|
-
|
|
1074
|
+
apiCalls: 1,
|
|
1039
1075
|
rateLimit: res.headers,
|
|
1040
1076
|
entities: { games: [game], ids: { playerId } },
|
|
1041
1077
|
data: {
|
|
@@ -1057,6 +1093,9 @@ Upstream cost: 1.`,
|
|
|
1057
1093
|
grandSlamTitles: num(summary.grand_slam_titles),
|
|
1058
1094
|
mastersTitles: num(summary.masters_titles),
|
|
1059
1095
|
finalsReached: num(summary.finals_reached),
|
|
1096
|
+
prizeMoneyUsd: num(summary.prize_money_usd),
|
|
1097
|
+
combined: careerScope(summary.combined),
|
|
1098
|
+
doubles: careerScope(summary.doubles),
|
|
1060
1099
|
},
|
|
1061
1100
|
bySurface,
|
|
1062
1101
|
byLevel,
|
package/dist/tools/rankings.js
CHANGED
|
@@ -60,6 +60,20 @@ function normalizeEdge(row) {
|
|
|
60
60
|
points: num(r.points),
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
|
+
export function normalizeCoverage(raw) {
|
|
64
|
+
const c = asRecord(raw);
|
|
65
|
+
if (!c)
|
|
66
|
+
return null;
|
|
67
|
+
const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : null);
|
|
68
|
+
return {
|
|
69
|
+
status: pickString(c.status) ?? null,
|
|
70
|
+
rankedPlayers: num(c.ranked_players),
|
|
71
|
+
deepestRank: num(c.deepest_rank),
|
|
72
|
+
missingRanks: num(c.missing_ranks),
|
|
73
|
+
completeThrough: num(c.complete_through),
|
|
74
|
+
note: pickString(c.note) ?? null,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
63
77
|
export const rankingsMovers = {
|
|
64
78
|
name: 'rankings_movers',
|
|
65
79
|
description: `Tennis ranking movers: biggest climbers/fallers between the latest ATP/WTA list and its predecessor, plus new entries and drop-outs.
|
|
@@ -73,9 +87,11 @@ Do not use when: current top-N snapshot → standings with game tennis; player h
|
|
|
73
87
|
|
|
74
88
|
Tennis-only. Direction up (climbers, default), down (fallers), or both (largest absolute change). Date pins an older ranking list (YYYY-MM-DD); default is the latest, which the API resolves past zero-diff cloned lists.
|
|
75
89
|
|
|
90
|
+
Some archived lists are held only in part (WTA 1975-86 above all). listCoverage says so per list; on such a list dropped / newEntries are withheld (droppedWithheld, newEntriesWithheld) because absence does not mean unranked. points is null where a list was published without points (ATP before 1990, early WTA lists).
|
|
91
|
+
|
|
76
92
|
The two lists are NOT always one week apart: previousDate is whatever the archive holds next, and the archive has holes. Read gapDays / comparisonWindow (and comparisonsAreWeekly) before describing a delta as weekly — a warning is raised whenever the gap exceeds ten days.
|
|
77
93
|
|
|
78
|
-
Parallel-safe: yes.
|
|
94
|
+
Parallel-safe: yes. API calls: 1.`,
|
|
79
95
|
inputSchema: {
|
|
80
96
|
type: 'object',
|
|
81
97
|
additionalProperties: false,
|
|
@@ -177,7 +193,7 @@ Parallel-safe: yes. Upstream cost: 1.`,
|
|
|
177
193
|
source: 'rankings_movers',
|
|
178
194
|
requestId,
|
|
179
195
|
tookMs: Date.now() - started,
|
|
180
|
-
|
|
196
|
+
apiCalls: 1,
|
|
181
197
|
httpStatus: res.status,
|
|
182
198
|
rateLimit: res.headers,
|
|
183
199
|
});
|
|
@@ -188,9 +204,14 @@ Parallel-safe: yes. Upstream cost: 1.`,
|
|
|
188
204
|
const newEntries = (Array.isArray(body.new_entries) ? body.new_entries : []).map(normalizeEdge);
|
|
189
205
|
const dropped = (Array.isArray(body.dropped) ? body.dropped : []).map(normalizeEdge);
|
|
190
206
|
const total = typeof body.total === 'number' ? body.total : items.length;
|
|
207
|
+
const coverage = asRecord(body.list_coverage) ?? null;
|
|
208
|
+
const droppedWithheld = body.dropped_withheld === true;
|
|
209
|
+
const newEntriesWithheld = body.new_entries_withheld === true;
|
|
191
210
|
const rankingDate = pickString(body.ranking_date) ?? null;
|
|
192
211
|
const previousDate = pickString(body.previous_date) ?? null;
|
|
193
212
|
const gapDays = (() => {
|
|
213
|
+
if (typeof body.gap_days === 'number')
|
|
214
|
+
return body.gap_days;
|
|
194
215
|
if (!rankingDate || !previousDate)
|
|
195
216
|
return null;
|
|
196
217
|
const a = Date.parse(rankingDate);
|
|
@@ -205,6 +226,10 @@ Parallel-safe: yes. Upstream cost: 1.`,
|
|
|
205
226
|
? 'week-over-week'
|
|
206
227
|
: `${Math.round(gapDays / 7)}-week gap`;
|
|
207
228
|
const warnings = [];
|
|
229
|
+
if (droppedWithheld || newEntriesWithheld) {
|
|
230
|
+
warnings.push(pickString(body.coverage_note) ??
|
|
231
|
+
'A compared list is held only in part, so drop-outs / new entries are withheld: absence from it does not mean unranked.');
|
|
232
|
+
}
|
|
208
233
|
if (gapDays !== null && gapDays > 10) {
|
|
209
234
|
warnings.push(`The compared lists are ${gapDays} days apart (${previousDate} -> ${rankingDate}), not one week. ` +
|
|
210
235
|
`Every "movement" here accumulated over ${Math.round(gapDays / 7)} weeks; there is no published ` +
|
|
@@ -223,7 +248,7 @@ Parallel-safe: yes. Upstream cost: 1.`,
|
|
|
223
248
|
game,
|
|
224
249
|
requestId,
|
|
225
250
|
tookMs: Date.now() - started,
|
|
226
|
-
|
|
251
|
+
apiCalls: 1,
|
|
227
252
|
rateLimit: res.headers,
|
|
228
253
|
warnings: warnings.length ? warnings : undefined,
|
|
229
254
|
data: {
|
|
@@ -239,6 +264,14 @@ Parallel-safe: yes. Upstream cost: 1.`,
|
|
|
239
264
|
items,
|
|
240
265
|
newEntries,
|
|
241
266
|
dropped,
|
|
267
|
+
droppedWithheld,
|
|
268
|
+
newEntriesWithheld,
|
|
269
|
+
listCoverage: coverage
|
|
270
|
+
? {
|
|
271
|
+
current: normalizeCoverage(coverage.current),
|
|
272
|
+
previous: normalizeCoverage(coverage.previous),
|
|
273
|
+
}
|
|
274
|
+
: null,
|
|
242
275
|
total,
|
|
243
276
|
},
|
|
244
277
|
});
|
|
@@ -262,7 +295,7 @@ pages itself. hasMore and oldestReturned/newestReturned say exactly which window
|
|
|
262
295
|
back, omittedOlder counts the rows left at the old end and omittedNewer the rows on
|
|
263
296
|
earlier (newer) pages.
|
|
264
297
|
|
|
265
|
-
Parallel-safe: yes.
|
|
298
|
+
Parallel-safe: yes. API calls: 1.`,
|
|
266
299
|
inputSchema: {
|
|
267
300
|
type: 'object',
|
|
268
301
|
additionalProperties: false,
|
|
@@ -337,7 +370,7 @@ Parallel-safe: yes. Upstream cost: 1.`,
|
|
|
337
370
|
source: 'player_rankings_history',
|
|
338
371
|
requestId,
|
|
339
372
|
tookMs: Date.now() - started,
|
|
340
|
-
|
|
373
|
+
apiCalls: 1,
|
|
341
374
|
httpStatus: res.status,
|
|
342
375
|
rateLimit: res.headers,
|
|
343
376
|
recover: ['Check the player id with search_entities'],
|
|
@@ -357,10 +390,10 @@ Parallel-safe: yes. Upstream cost: 1.`,
|
|
|
357
390
|
const inWindow = sinceRaw
|
|
358
391
|
? allHistory.filter((h) => h.date !== null && h.date >= sinceRaw)
|
|
359
392
|
: allHistory;
|
|
360
|
-
const
|
|
393
|
+
const pagedApi = typeof body.total === 'number' && typeof body.order === 'string';
|
|
361
394
|
let total;
|
|
362
395
|
let history;
|
|
363
|
-
if (
|
|
396
|
+
if (pagedApi) {
|
|
364
397
|
total = body.total;
|
|
365
398
|
history = body.order === 'desc' ? [...inWindow].reverse() : inWindow;
|
|
366
399
|
}
|
|
@@ -371,7 +404,7 @@ Parallel-safe: yes. Upstream cost: 1.`,
|
|
|
371
404
|
}
|
|
372
405
|
const omittedNewer = Math.min(total, (page - 1) * limit);
|
|
373
406
|
const omittedOlder = Math.max(0, total - omittedNewer - history.length);
|
|
374
|
-
const hasMore =
|
|
407
|
+
const hasMore = pagedApi && typeof body.has_next === 'boolean' ? body.has_next : omittedOlder > 0;
|
|
375
408
|
const best = history.reduce((acc, h) => {
|
|
376
409
|
if (h.rank === null)
|
|
377
410
|
return acc;
|
|
@@ -392,7 +425,7 @@ Parallel-safe: yes. Upstream cost: 1.`,
|
|
|
392
425
|
game,
|
|
393
426
|
requestId,
|
|
394
427
|
tookMs: Date.now() - started,
|
|
395
|
-
|
|
428
|
+
apiCalls: 1,
|
|
396
429
|
rateLimit: res.headers,
|
|
397
430
|
data: {
|
|
398
431
|
title: `${pickString(body.player_name) ?? playerId} ranking history`,
|