@claudinho/core 0.9.2 → 0.9.4
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 +1 -1
- package/dist/index.d.ts +422 -167
- package/dist/index.js +1087 -332
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -149,10 +149,11 @@ var ALIASES = [
|
|
|
149
149
|
["Burma", "MM"],
|
|
150
150
|
["Cape Verde", "CV"]
|
|
151
151
|
];
|
|
152
|
-
var BY_NATION = Object.
|
|
153
|
-
|
|
152
|
+
var BY_NATION = Object.assign(
|
|
153
|
+
/* @__PURE__ */ Object.create(null),
|
|
154
|
+
Object.fromEntries([...NATIONS, ...ALIASES].map(([name, code]) => [norm(name), code]))
|
|
154
155
|
);
|
|
155
|
-
var BY_CODE = {
|
|
156
|
+
var BY_CODE = Object.assign(/* @__PURE__ */ Object.create(null), {
|
|
156
157
|
MEX: "MX",
|
|
157
158
|
RSA: "ZA",
|
|
158
159
|
KOR: "KR",
|
|
@@ -213,7 +214,7 @@ var BY_CODE = {
|
|
|
213
214
|
HON: "HN",
|
|
214
215
|
COD: "CD",
|
|
215
216
|
MLI: "ML"
|
|
216
|
-
};
|
|
217
|
+
});
|
|
217
218
|
function nationToFlag(nameOrCode) {
|
|
218
219
|
const region = nationToRegion(nameOrCode);
|
|
219
220
|
return region ? flagEmoji(region) : NEUTRAL;
|
|
@@ -221,7 +222,7 @@ function nationToFlag(nameOrCode) {
|
|
|
221
222
|
var INTL_BY_NAME;
|
|
222
223
|
function intlNameMap() {
|
|
223
224
|
if (INTL_BY_NAME) return INTL_BY_NAME;
|
|
224
|
-
const map =
|
|
225
|
+
const map = /* @__PURE__ */ Object.create(null);
|
|
225
226
|
try {
|
|
226
227
|
const dn = new Intl.DisplayNames(["en"], { type: "region" });
|
|
227
228
|
for (let a = 65; a <= 90; a++) {
|
|
@@ -246,11 +247,8 @@ function intlNameMap() {
|
|
|
246
247
|
}
|
|
247
248
|
function nationToRegion(nameOrCode) {
|
|
248
249
|
if (!nameOrCode) return void 0;
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
const byCode = BY_CODE[nameOrCode.trim().toUpperCase()];
|
|
252
|
-
if (byCode) return byCode;
|
|
253
|
-
return intlNameMap()[norm(nameOrCode)];
|
|
250
|
+
const str = (v) => typeof v === "string" && v ? v : void 0;
|
|
251
|
+
return str(BY_NATION[norm(nameOrCode)]) ?? str(BY_CODE[nameOrCode.trim().toUpperCase()]) ?? str(intlNameMap()[norm(nameOrCode)]);
|
|
254
252
|
}
|
|
255
253
|
|
|
256
254
|
// src/i18n.ts
|
|
@@ -272,6 +270,7 @@ var EN = {
|
|
|
272
270
|
"bracket.slot.loser": "{stage} {n} loser",
|
|
273
271
|
"bracket.slot.tbd": "TBD",
|
|
274
272
|
"live.data": "Live data: {source}",
|
|
273
|
+
"standings.unavailable": "Live standings unavailable.",
|
|
275
274
|
"share.tryIt": "Try it: {line}",
|
|
276
275
|
"stage.group": "Group {group}",
|
|
277
276
|
"stage.groupStage": "Group stage",
|
|
@@ -301,6 +300,7 @@ var ES = {
|
|
|
301
300
|
"bracket.slot.loser": "Perdedor {stage} {n}",
|
|
302
301
|
"bracket.slot.tbd": "Por definir",
|
|
303
302
|
"live.data": "Datos en vivo: {source}",
|
|
303
|
+
"standings.unavailable": "Tabla en vivo no disponible.",
|
|
304
304
|
"share.tryIt": "Pru\xE9balo: {line}",
|
|
305
305
|
"stage.group": "Grupo {group}",
|
|
306
306
|
"stage.groupStage": "Fase de grupos",
|
|
@@ -330,6 +330,7 @@ var PT = {
|
|
|
330
330
|
"bracket.slot.loser": "Perdedor {stage} {n}",
|
|
331
331
|
"bracket.slot.tbd": "A definir",
|
|
332
332
|
"live.data": "Dados ao vivo: {source}",
|
|
333
|
+
"standings.unavailable": "Classifica\xE7\xE3o ao vivo indispon\xEDvel.",
|
|
333
334
|
"share.tryIt": "Experimente: {line}",
|
|
334
335
|
"stage.group": "Grupo {group}",
|
|
335
336
|
"stage.groupStage": "Fase de grupos",
|
|
@@ -359,6 +360,7 @@ var FR = {
|
|
|
359
360
|
"bracket.slot.loser": "Perdant {stage} {n}",
|
|
360
361
|
"bracket.slot.tbd": "\xC0 d\xE9finir",
|
|
361
362
|
"live.data": "Donn\xE9es en direct : {source}",
|
|
363
|
+
"standings.unavailable": "Classement en direct indisponible.",
|
|
362
364
|
"share.tryIt": "Essayez : {line}",
|
|
363
365
|
"stage.group": "Groupe {group}",
|
|
364
366
|
"stage.groupStage": "Phase de groupes",
|
|
@@ -447,7 +449,14 @@ function safeLocale(locale) {
|
|
|
447
449
|
return "en";
|
|
448
450
|
}
|
|
449
451
|
}
|
|
452
|
+
function parsedDate(iso) {
|
|
453
|
+
const t2 = Date.parse(iso);
|
|
454
|
+
return Number.isFinite(t2) ? new Date(t2) : void 0;
|
|
455
|
+
}
|
|
456
|
+
var UNKNOWN_TIME = "\u2014";
|
|
450
457
|
function formatKickoff(iso, opts = {}) {
|
|
458
|
+
const when = parsedDate(iso);
|
|
459
|
+
if (!when) return UNKNOWN_TIME;
|
|
451
460
|
const tz = resolveTz(opts.tz);
|
|
452
461
|
const locale = safeLocale(opts.locale);
|
|
453
462
|
return new Intl.DateTimeFormat(locale, {
|
|
@@ -457,18 +466,22 @@ function formatKickoff(iso, opts = {}) {
|
|
|
457
466
|
minute: "2-digit",
|
|
458
467
|
hour12: false,
|
|
459
468
|
timeZone: tz
|
|
460
|
-
}).format(
|
|
469
|
+
}).format(when);
|
|
461
470
|
}
|
|
462
471
|
function formatDate(iso, opts = {}) {
|
|
472
|
+
const when = parsedDate(iso);
|
|
473
|
+
if (!when) return UNKNOWN_TIME;
|
|
463
474
|
const tz = resolveTz(opts.tz);
|
|
464
475
|
const locale = safeLocale(opts.locale);
|
|
465
476
|
return new Intl.DateTimeFormat(locale, {
|
|
466
477
|
month: "short",
|
|
467
478
|
day: "numeric",
|
|
468
479
|
timeZone: tz
|
|
469
|
-
}).format(
|
|
480
|
+
}).format(when);
|
|
470
481
|
}
|
|
471
482
|
function formatTime(iso, opts = {}) {
|
|
483
|
+
const when = parsedDate(iso);
|
|
484
|
+
if (!when) return UNKNOWN_TIME;
|
|
472
485
|
const tz = resolveTz(opts.tz);
|
|
473
486
|
const locale = safeLocale(opts.locale);
|
|
474
487
|
return new Intl.DateTimeFormat(locale, {
|
|
@@ -476,10 +489,12 @@ function formatTime(iso, opts = {}) {
|
|
|
476
489
|
minute: "2-digit",
|
|
477
490
|
hour12: false,
|
|
478
491
|
timeZone: tz
|
|
479
|
-
}).format(
|
|
492
|
+
}).format(when);
|
|
480
493
|
}
|
|
481
494
|
function countdown(iso, from = /* @__PURE__ */ new Date()) {
|
|
482
|
-
const
|
|
495
|
+
const when = parsedDate(iso);
|
|
496
|
+
if (!when) return UNKNOWN_TIME;
|
|
497
|
+
const ms = when.getTime() - from.getTime();
|
|
483
498
|
if (ms <= 0) return "now";
|
|
484
499
|
const totalMin = Math.floor(ms / 6e4);
|
|
485
500
|
const days = Math.floor(totalMin / 1440);
|
|
@@ -490,75 +505,58 @@ function countdown(iso, from = /* @__PURE__ */ new Date()) {
|
|
|
490
505
|
return `${mins}m`;
|
|
491
506
|
}
|
|
492
507
|
function localDate(iso, tz) {
|
|
508
|
+
const when = parsedDate(iso);
|
|
509
|
+
if (!when) return "";
|
|
493
510
|
const zone = resolveTz(tz);
|
|
494
511
|
return new Intl.DateTimeFormat("en-CA", {
|
|
495
512
|
year: "numeric",
|
|
496
513
|
month: "2-digit",
|
|
497
514
|
day: "2-digit",
|
|
498
515
|
timeZone: zone
|
|
499
|
-
}).format(
|
|
516
|
+
}).format(when);
|
|
500
517
|
}
|
|
501
518
|
function shiftUtcDate(dateISO, days) {
|
|
502
519
|
const [y, m, d] = dateISO.slice(0, 10).split("-").map(Number);
|
|
503
520
|
return new Date(Date.UTC(y ?? 1970, (m ?? 1) - 1, (d ?? 1) + days)).toISOString().slice(0, 10);
|
|
504
521
|
}
|
|
505
522
|
|
|
506
|
-
// src/sanitize.ts
|
|
507
|
-
var FEED_TEXT_MAX = 100;
|
|
508
|
-
function sanitizeFeedText(value, max = FEED_TEXT_MAX) {
|
|
509
|
-
let out = "";
|
|
510
|
-
let count = 0;
|
|
511
|
-
for (const ch of String(value)) {
|
|
512
|
-
const cp = ch.codePointAt(0) ?? 0;
|
|
513
|
-
const isWhitespaceControl = cp === 9 || cp === 10 || cp === 13;
|
|
514
|
-
if ((cp <= 31 || cp >= 127 && cp <= 159) && !isWhitespaceControl) continue;
|
|
515
|
-
if (count >= max) break;
|
|
516
|
-
out += isWhitespaceControl ? " " : ch;
|
|
517
|
-
count++;
|
|
518
|
-
}
|
|
519
|
-
return out;
|
|
520
|
-
}
|
|
521
|
-
function sanitizeTeam(t2) {
|
|
522
|
-
return {
|
|
523
|
-
...t2 ?? {},
|
|
524
|
-
code: sanitizeFeedText(t2?.code ?? ""),
|
|
525
|
-
name: sanitizeFeedText(t2?.name ?? ""),
|
|
526
|
-
flag: sanitizeFeedText(t2?.flag ?? "")
|
|
527
|
-
};
|
|
528
|
-
}
|
|
529
|
-
function finiteOrUndefined(v) {
|
|
530
|
-
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
531
|
-
}
|
|
532
|
-
function sanitizeScorePair(v) {
|
|
533
|
-
const home = finiteOrUndefined(v?.home);
|
|
534
|
-
const away = finiteOrUndefined(v?.away);
|
|
535
|
-
return home !== void 0 && away !== void 0 ? { home, away } : void 0;
|
|
536
|
-
}
|
|
537
|
-
function sanitizeMatchStrings(m) {
|
|
538
|
-
const score = sanitizeScorePair(m.score);
|
|
539
|
-
return {
|
|
540
|
-
...m,
|
|
541
|
-
venue: sanitizeFeedText(m.venue ?? ""),
|
|
542
|
-
city: m.city == null ? m.city : sanitizeFeedText(m.city),
|
|
543
|
-
country: m.country == null ? m.country : sanitizeFeedText(m.country),
|
|
544
|
-
home: sanitizeTeam(m.home),
|
|
545
|
-
away: sanitizeTeam(m.away),
|
|
546
|
-
score,
|
|
547
|
-
shootout: score ? sanitizeScorePair(m.shootout) : void 0,
|
|
548
|
-
minute: finiteOrUndefined(m.minute)
|
|
549
|
-
};
|
|
550
|
-
}
|
|
551
|
-
|
|
552
523
|
// src/text.ts
|
|
553
524
|
var segmenter = new Intl.Segmenter();
|
|
554
525
|
var WIDE_CLUSTER = new RegExp("^(?:\\p{Regional_Indicator}|\\p{Extended_Pictographic})", "u");
|
|
526
|
+
var WIDE_BASE = /[ᄀ-ᅟ⺀-〾ぁ-㏿㐀-䶿一-鿿ꀀ-ꥠ-가-힣豈-︐-︙︰--⦆¢-₩\u{20000}-\u{2FFFD}\u{30000}-\u{3FFFD}]/u;
|
|
527
|
+
var KEYCAP = /\u{20E3}/u;
|
|
528
|
+
var ZERO_WIDTH_BASE = /[\p{Mn}\p{Me}\p{Cf}\p{Cc}]/u;
|
|
529
|
+
function clusterWidth(segment) {
|
|
530
|
+
if (WIDE_CLUSTER.test(segment) || KEYCAP.test(segment)) return 2;
|
|
531
|
+
const first = segment.codePointAt(0);
|
|
532
|
+
if (first === void 0) return 0;
|
|
533
|
+
const base = String.fromCodePoint(first);
|
|
534
|
+
if (ZERO_WIDTH_BASE.test(base)) return 0;
|
|
535
|
+
return WIDE_BASE.test(base) ? 2 : 1;
|
|
536
|
+
}
|
|
555
537
|
function displayWidth(s) {
|
|
556
538
|
let w = 0;
|
|
557
539
|
for (const { segment } of segmenter.segment(s)) {
|
|
558
|
-
w +=
|
|
540
|
+
w += clusterWidth(segment);
|
|
559
541
|
}
|
|
560
542
|
return w;
|
|
561
543
|
}
|
|
544
|
+
function truncateVisible(s, maxColumns, marker = "\u2026") {
|
|
545
|
+
if (displayWidth(s) <= maxColumns) return s;
|
|
546
|
+
const budget = Math.max(0, maxColumns - displayWidth(marker));
|
|
547
|
+
let out = "";
|
|
548
|
+
let w = 0;
|
|
549
|
+
for (const { segment } of segmenter.segment(s)) {
|
|
550
|
+
const cw = clusterWidth(segment);
|
|
551
|
+
if (w + cw > budget) break;
|
|
552
|
+
out += segment;
|
|
553
|
+
w += cw;
|
|
554
|
+
}
|
|
555
|
+
return out + marker;
|
|
556
|
+
}
|
|
557
|
+
function* graphemes(s) {
|
|
558
|
+
for (const { segment } of segmenter.segment(s)) yield segment;
|
|
559
|
+
}
|
|
562
560
|
function padVisible(s, width) {
|
|
563
561
|
const w = displayWidth(s);
|
|
564
562
|
return w >= width ? s : s + " ".repeat(width - w);
|
|
@@ -2945,6 +2943,14 @@ var KNOCKOUT_EXTRA_TIME_MS = 60 * 6e4;
|
|
|
2945
2943
|
function liveWindowMsFor(m) {
|
|
2946
2944
|
return m.stage === "GROUP" || m.stage === "FRIENDLY" ? LIVE_WINDOW_MS : LIVE_WINDOW_MS + KNOCKOUT_EXTRA_TIME_MS;
|
|
2947
2945
|
}
|
|
2946
|
+
function isTournamentWindowOver(now = Date.now(), fixtures = SCHEDULE) {
|
|
2947
|
+
if (fixtures.length === 0) return false;
|
|
2948
|
+
return fixtures.every((m) => {
|
|
2949
|
+
const k = Date.parse(m.kickoff);
|
|
2950
|
+
if (Number.isNaN(k)) return false;
|
|
2951
|
+
return now > k + liveWindowMsFor(m);
|
|
2952
|
+
});
|
|
2953
|
+
}
|
|
2948
2954
|
function fixturesInLiveWindow(now = Date.now(), fixtures = SCHEDULE) {
|
|
2949
2955
|
return fixtures.filter((m) => {
|
|
2950
2956
|
const k = Date.parse(m.kickoff);
|
|
@@ -3086,48 +3092,318 @@ function rosterAtZero(matches) {
|
|
|
3086
3092
|
return [...teams.values()].sort((a, b) => a.name.localeCompare(b.name)).map(blankRow);
|
|
3087
3093
|
}
|
|
3088
3094
|
|
|
3089
|
-
// src/
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3095
|
+
// src/trust/bounded.ts
|
|
3096
|
+
function bounded(items, max, complete = true) {
|
|
3097
|
+
const kept = items.length > max ? items.slice(0, max) : items;
|
|
3098
|
+
return {
|
|
3099
|
+
items: kept,
|
|
3100
|
+
total: items.length,
|
|
3101
|
+
shown: kept.length,
|
|
3102
|
+
truncated: items.length > kept.length,
|
|
3103
|
+
complete
|
|
3104
|
+
};
|
|
3097
3105
|
}
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3106
|
+
function takeBounded(value, max) {
|
|
3107
|
+
if (!Array.isArray(value)) return [];
|
|
3108
|
+
return value.length > max ? value.slice(0, max) : value;
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
// src/trust/result.ts
|
|
3112
|
+
var valid = (value) => ({ kind: "valid", value });
|
|
3113
|
+
var definitiveNone = (reason) => ({
|
|
3114
|
+
kind: "definitive-none",
|
|
3115
|
+
reason
|
|
3116
|
+
});
|
|
3117
|
+
var malformed = (reason) => ({ kind: "malformed", reason });
|
|
3118
|
+
var ambiguous = (reason) => ({ kind: "ambiguous", reason });
|
|
3119
|
+
var unresolved = (reason) => ({ kind: "unresolved", reason });
|
|
3120
|
+
function parsedValue(r) {
|
|
3121
|
+
return r.kind === "valid" ? r.value : void 0;
|
|
3122
|
+
}
|
|
3123
|
+
function isCacheable(r) {
|
|
3124
|
+
return r.kind === "valid" || r.kind === "definitive-none" || r.kind === "ambiguous";
|
|
3125
|
+
}
|
|
3126
|
+
|
|
3127
|
+
// src/trust/roles.ts
|
|
3128
|
+
var FORBIDDEN_IN_LABEL = new RegExp("[\\p{Cc}\\p{Cf}\\p{Zl}\\p{Zp}\\p{Cs}\\p{Co}\\p{Cn}]|\\p{Default_Ignorable_Code_Point}", "u");
|
|
3129
|
+
var EMOJI_IN_LABEL = new RegExp("\\p{Extended_Pictographic}|\\p{Regional_Indicator}|\\p{Emoji_Modifier}|\\u{20E3}", "u");
|
|
3130
|
+
var MAX_LABEL_INPUT_UNITS = 4096;
|
|
3131
|
+
var MAX_LABEL_COLUMNS = 100;
|
|
3132
|
+
function humanLabel(value, maxColumns = MAX_LABEL_COLUMNS) {
|
|
3133
|
+
if (typeof value !== "string" || value === "") return "";
|
|
3134
|
+
const capped = value.length > MAX_LABEL_INPUT_UNITS ? value.slice(0, MAX_LABEL_INPUT_UNITS) : value;
|
|
3135
|
+
return visible(runToFixedPoint(capped, maxColumns));
|
|
3136
|
+
}
|
|
3137
|
+
function visible(label) {
|
|
3138
|
+
return label !== "" && displayWidth(label) === 0 ? "" : label;
|
|
3139
|
+
}
|
|
3140
|
+
function runToFixedPoint(capped, maxColumns) {
|
|
3141
|
+
const first = sealLabelOnce(capped, maxColumns);
|
|
3142
|
+
if (!lastPassDropped) return first;
|
|
3143
|
+
let out = first;
|
|
3144
|
+
for (let pass = 0; pass < 3; pass++) {
|
|
3145
|
+
const again = sealLabelOnce(out, maxColumns);
|
|
3146
|
+
if (again === out) return out;
|
|
3147
|
+
out = again;
|
|
3108
3148
|
}
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3149
|
+
return sealLabelOnce(out, maxColumns) === out ? out : "";
|
|
3150
|
+
}
|
|
3151
|
+
var lastPassDropped = false;
|
|
3152
|
+
function sealLabelOnce(value, maxColumns) {
|
|
3153
|
+
lastPassDropped = false;
|
|
3154
|
+
let normalized;
|
|
3155
|
+
try {
|
|
3156
|
+
normalized = value.normalize("NFC");
|
|
3157
|
+
} catch {
|
|
3158
|
+
return "";
|
|
3112
3159
|
}
|
|
3113
|
-
|
|
3160
|
+
const maxCodePoints = Math.max(16, maxColumns * 4);
|
|
3161
|
+
let out = "";
|
|
3162
|
+
let width = 0;
|
|
3163
|
+
let points = 0;
|
|
3164
|
+
for (const cluster of graphemes(normalized)) {
|
|
3165
|
+
if ([...cluster].length > 8) {
|
|
3166
|
+
lastPassDropped = true;
|
|
3167
|
+
continue;
|
|
3168
|
+
}
|
|
3169
|
+
if (EMOJI_IN_LABEL.test(cluster)) {
|
|
3170
|
+
lastPassDropped = true;
|
|
3171
|
+
continue;
|
|
3172
|
+
}
|
|
3173
|
+
let piece = "";
|
|
3174
|
+
for (const ch of cluster) {
|
|
3175
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
3176
|
+
if (cp === 9 || cp === 10 || cp === 13) {
|
|
3177
|
+
piece += " ";
|
|
3178
|
+
continue;
|
|
3179
|
+
}
|
|
3180
|
+
if (FORBIDDEN_IN_LABEL.test(ch)) {
|
|
3181
|
+
lastPassDropped = true;
|
|
3182
|
+
continue;
|
|
3183
|
+
}
|
|
3184
|
+
piece += ch;
|
|
3185
|
+
}
|
|
3186
|
+
if (!piece) {
|
|
3187
|
+
lastPassDropped = true;
|
|
3188
|
+
continue;
|
|
3189
|
+
}
|
|
3190
|
+
const w = displayWidth(piece);
|
|
3191
|
+
const cps = [...piece].length;
|
|
3192
|
+
if (width + w > maxColumns || points + cps > maxCodePoints) break;
|
|
3193
|
+
out += piece;
|
|
3194
|
+
width += w;
|
|
3195
|
+
points += cps;
|
|
3196
|
+
}
|
|
3197
|
+
try {
|
|
3198
|
+
return out.normalize("NFC").trim();
|
|
3199
|
+
} catch {
|
|
3200
|
+
return out.trim();
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
function opaqueId(value, grammar) {
|
|
3204
|
+
return typeof value === "string" && grammar.test(value) ? value : void 0;
|
|
3205
|
+
}
|
|
3206
|
+
var ESPN_ID = /^[0-9]{1,20}$/;
|
|
3207
|
+
var ISO_INSTANT = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:[Zz]|[+-]\d{2}(?::?\d{2})?)$/;
|
|
3208
|
+
var ISO_CANONICAL = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
3209
|
+
function calendarValid(iso) {
|
|
3210
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(iso);
|
|
3211
|
+
if (!m) return false;
|
|
3212
|
+
const year = Number(m[1]);
|
|
3213
|
+
const month = Number(m[2]);
|
|
3214
|
+
const day = Number(m[3]);
|
|
3215
|
+
if (month < 1 || month > 12 || day < 1) return false;
|
|
3216
|
+
return day <= new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
3217
|
+
}
|
|
3218
|
+
function canonicalTimestamp(value) {
|
|
3219
|
+
if (typeof value !== "string" || !ISO_INSTANT.test(value)) return void 0;
|
|
3220
|
+
if (!calendarValid(value)) return void 0;
|
|
3221
|
+
const t2 = Date.parse(value);
|
|
3222
|
+
if (!Number.isFinite(t2)) return void 0;
|
|
3223
|
+
const out = new Date(t2).toISOString();
|
|
3224
|
+
return ISO_CANONICAL.test(out) ? out : void 0;
|
|
3225
|
+
}
|
|
3226
|
+
function productFlag(nameOrCode) {
|
|
3227
|
+
return nationToFlag(nameOrCode);
|
|
3228
|
+
}
|
|
3229
|
+
function count(value, max) {
|
|
3230
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= max ? value : void 0;
|
|
3231
|
+
}
|
|
3232
|
+
function quantity(value) {
|
|
3233
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
3234
|
+
}
|
|
3235
|
+
function probability(value) {
|
|
3236
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1 ? value : void 0;
|
|
3237
|
+
}
|
|
3238
|
+
function member(value, allowed) {
|
|
3239
|
+
return typeof value === "string" && allowed.has(value) ? value : void 0;
|
|
3240
|
+
}
|
|
3241
|
+
function flag(value) {
|
|
3242
|
+
return typeof value === "boolean" ? value : void 0;
|
|
3243
|
+
}
|
|
3244
|
+
|
|
3245
|
+
// src/trust/match.ts
|
|
3246
|
+
var MAX_GOALS = 99;
|
|
3247
|
+
var MAX_MINUTE = 200;
|
|
3248
|
+
var MAX_MATCH_EVENTS = 128;
|
|
3249
|
+
var TEAM_CODE_COLUMNS = 8;
|
|
3250
|
+
var STAGES = /* @__PURE__ */ new Set(["GROUP", "R32", "R16", "QF", "SF", "3P", "F", "FRIENDLY"]);
|
|
3251
|
+
var STATUSES = /* @__PURE__ */ new Set(["SCHEDULED", "LIVE", "HT", "FT", "POSTPONED", "CANCELLED"]);
|
|
3252
|
+
var EVENT_TYPES = /* @__PURE__ */ new Set(["GOAL", "OWN_GOAL", "PEN", "YELLOW", "RED", "SUB"]);
|
|
3253
|
+
function teamCode(raw, fallbackName) {
|
|
3254
|
+
const upper = typeof raw === "string" ? raw.toUpperCase() : raw;
|
|
3255
|
+
const stated = humanLabel(upper, TEAM_CODE_COLUMNS);
|
|
3256
|
+
if (stated) return stated;
|
|
3257
|
+
return humanLabel([...fallbackName].slice(0, 3).join("").toUpperCase(), TEAM_CODE_COLUMNS);
|
|
3258
|
+
}
|
|
3259
|
+
function sealTeam(raw) {
|
|
3260
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
3261
|
+
const t2 = raw;
|
|
3262
|
+
const name = humanLabel(t2.name);
|
|
3263
|
+
if (!name) return void 0;
|
|
3264
|
+
const code = teamCode(t2.code, name);
|
|
3265
|
+
return { code, name, flag: productFlag(name) };
|
|
3266
|
+
}
|
|
3267
|
+
function sealScorePair(raw) {
|
|
3268
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
3269
|
+
const v = raw;
|
|
3270
|
+
const home = count(v.home, MAX_GOALS);
|
|
3271
|
+
const away = count(v.away, MAX_GOALS);
|
|
3272
|
+
return home !== void 0 && away !== void 0 ? { home, away } : void 0;
|
|
3273
|
+
}
|
|
3274
|
+
function sealEvent(raw) {
|
|
3275
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
3276
|
+
const e = raw;
|
|
3277
|
+
const type = member(e.type, EVENT_TYPES);
|
|
3278
|
+
if (!type) return void 0;
|
|
3279
|
+
const minute = count(e.minute, MAX_MINUTE);
|
|
3280
|
+
if (minute === void 0) return void 0;
|
|
3281
|
+
const out = { type, minute, teamCode: humanLabel(e.teamCode, TEAM_CODE_COLUMNS) };
|
|
3282
|
+
const player = humanLabel(e.player);
|
|
3283
|
+
if (player) out.player = player;
|
|
3284
|
+
return out;
|
|
3285
|
+
}
|
|
3286
|
+
function sealMatch(parts, opts = {}) {
|
|
3287
|
+
if (!parts || typeof parts !== "object") return malformed("match is not an object");
|
|
3288
|
+
const id = opaqueId(parts.id, ESPN_ID);
|
|
3289
|
+
if (!id) return malformed("match id is absent or not an identifier");
|
|
3290
|
+
const kickoff = canonicalTimestamp(parts.kickoff);
|
|
3291
|
+
if (!kickoff) return malformed("match kickoff is absent or not one instant");
|
|
3292
|
+
const stage = member(parts.stage, STAGES);
|
|
3293
|
+
if (!stage) return malformed("match stage is not a known stage");
|
|
3294
|
+
const status = member(parts.status, STATUSES);
|
|
3295
|
+
if (!status) return malformed("match status is not a known status");
|
|
3296
|
+
const home = sealTeam(parts.home);
|
|
3297
|
+
const away = sealTeam(parts.away);
|
|
3298
|
+
if (!home || !away) return malformed("match does not name both teams");
|
|
3299
|
+
if (home.code === away.code && home.name === away.name) {
|
|
3300
|
+
return definitiveNone("both competitors are the same team");
|
|
3301
|
+
}
|
|
3302
|
+
const group = humanLabel(parts.group) || void 0;
|
|
3303
|
+
const city = humanLabel(parts.city) || void 0;
|
|
3304
|
+
const country = humanLabel(parts.country) || void 0;
|
|
3305
|
+
const score = status === "SCHEDULED" ? void 0 : sealScorePair(parts.score);
|
|
3306
|
+
if ((status === "LIVE" || status === "HT" || status === "FT") && !score) {
|
|
3307
|
+
return malformed("match claims an unreadable score");
|
|
3308
|
+
}
|
|
3309
|
+
const finished = status === "FT";
|
|
3310
|
+
const canGoToPenalties = stage !== "GROUP";
|
|
3311
|
+
const shootoutPresent = parts.shootout !== void 0 && parts.shootout !== null;
|
|
3312
|
+
const parsedShootout = shootoutPresent ? sealScorePair(parts.shootout) : void 0;
|
|
3313
|
+
const shootoutStatus = status === "LIVE" || status === "FT";
|
|
3314
|
+
const shootout = parsedShootout && score && canGoToPenalties && shootoutStatus && !(finished && parsedShootout.home === parsedShootout.away) ? parsedShootout : void 0;
|
|
3315
|
+
const claimedWinner = teamCode(parts.winnerCode, "");
|
|
3316
|
+
const claimedSide = claimedWinner === home.code ? "home" : claimedWinner === away.code ? "away" : void 0;
|
|
3317
|
+
let winnerCode;
|
|
3318
|
+
const unusableShootoutCouldDecide = canGoToPenalties && shootoutPresent && !shootout;
|
|
3319
|
+
if (claimedSide && score && finished && !unusableShootoutCouldDecide) {
|
|
3320
|
+
const level = score.home === score.away;
|
|
3321
|
+
const decider = shootout ?? score;
|
|
3322
|
+
if (decider && decider.home !== decider.away) {
|
|
3323
|
+
if ((decider.home > decider.away ? "home" : "away") === claimedSide) winnerCode = claimedWinner;
|
|
3324
|
+
} else if (level && !shootoutPresent && canGoToPenalties) {
|
|
3325
|
+
winnerCode = claimedWinner;
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
const events = opts.events === false ? [] : takeBounded(parts.events, MAX_MATCH_EVENTS).map(sealEvent).filter((e) => !!e);
|
|
3329
|
+
const out = { id, stage };
|
|
3330
|
+
if (group) out.group = group;
|
|
3331
|
+
out.kickoff = kickoff;
|
|
3332
|
+
out.venue = humanLabel(parts.venue);
|
|
3333
|
+
if (city) out.city = city;
|
|
3334
|
+
if (country) out.country = country;
|
|
3335
|
+
out.home = home;
|
|
3336
|
+
out.away = away;
|
|
3337
|
+
if (score) out.score = score;
|
|
3338
|
+
if (shootout) out.shootout = shootout;
|
|
3339
|
+
const minute = count(parts.minute, MAX_MINUTE);
|
|
3340
|
+
if (minute !== void 0) out.minute = minute;
|
|
3341
|
+
out.status = status;
|
|
3342
|
+
if (events.length) out.events = events;
|
|
3343
|
+
if (winnerCode) out.winnerCode = winnerCode;
|
|
3344
|
+
out.updatedAt = canonicalTimestamp(parts.updatedAt) ?? "";
|
|
3345
|
+
return valid(out);
|
|
3346
|
+
}
|
|
3347
|
+
function parseCachedMatch(raw, opts = {}) {
|
|
3348
|
+
if (!raw || typeof raw !== "object") return definitiveNone("cache entry is not an object");
|
|
3349
|
+
return sealMatch(raw, opts);
|
|
3350
|
+
}
|
|
3351
|
+
function parseCachedMatches(raw, max, opts = {}) {
|
|
3352
|
+
const total = Array.isArray(raw) ? raw.length : 0;
|
|
3353
|
+
const considered = takeBounded(raw, max);
|
|
3354
|
+
const items = considered.map((m) => parseCachedMatch(m, opts)).flatMap((r) => r.kind === "valid" ? [r.value] : []);
|
|
3355
|
+
return {
|
|
3356
|
+
items,
|
|
3357
|
+
total,
|
|
3358
|
+
shown: items.length,
|
|
3359
|
+
truncated: total > considered.length,
|
|
3360
|
+
// Some record in the window was unreadable, or the window did not cover the
|
|
3361
|
+
// file — either way we are not reporting on all of it.
|
|
3362
|
+
complete: items.length === total
|
|
3363
|
+
};
|
|
3364
|
+
}
|
|
3365
|
+
|
|
3366
|
+
// src/trust/espn.ts
|
|
3367
|
+
var MAX_EVENTS = 300;
|
|
3368
|
+
var MAX_GROUPS = 16;
|
|
3369
|
+
var MAX_GROUP_ROWS = 32;
|
|
3370
|
+
var STAGES2 = /* @__PURE__ */ new Set(["GROUP", "R32", "R16", "QF", "SF", "3P", "F", "FRIENDLY"]);
|
|
3371
|
+
function teamNames(t2) {
|
|
3372
|
+
return [t2?.displayName, t2?.name, t2?.location, t2?.shortDisplayName, t2?.abbreviation].map((v) => humanLabel(v)).filter((v) => v !== "");
|
|
3373
|
+
}
|
|
3374
|
+
function toParticipant(raw) {
|
|
3375
|
+
if (!raw || typeof raw !== "object") return malformed("competitor is not an object");
|
|
3376
|
+
const names = teamNames(raw.team);
|
|
3377
|
+
if (names.length === 0) return malformed("competitor names no team");
|
|
3378
|
+
const name = names[0];
|
|
3379
|
+
const code = teamCode(raw.team?.abbreviation, name);
|
|
3380
|
+
const team = { code, name, flag: productFlag(name) };
|
|
3381
|
+
const providerId = opaqueId(raw.team?.id, ESPN_ID);
|
|
3382
|
+
const known = productFlag(name) !== nationToFlag("");
|
|
3383
|
+
return valid(
|
|
3384
|
+
providerId && known ? { kind: "team", providerId, team } : { kind: "slot", team }
|
|
3385
|
+
);
|
|
3386
|
+
}
|
|
3114
3387
|
function mapStatus(st) {
|
|
3115
|
-
const
|
|
3116
|
-
const
|
|
3388
|
+
const type = st?.type;
|
|
3389
|
+
const name = typeof type?.name === "string" ? type.name.toUpperCase() : "";
|
|
3390
|
+
const state = typeof type?.state === "string" ? type.state : "";
|
|
3117
3391
|
if (name.includes("HALFTIME")) return "HT";
|
|
3118
3392
|
if (name.includes("POSTPONED")) return "POSTPONED";
|
|
3119
3393
|
if (name.includes("CANCEL")) return "CANCELLED";
|
|
3120
3394
|
if (state === "pre") return "SCHEDULED";
|
|
3121
3395
|
if (state === "post") return "FT";
|
|
3122
3396
|
if (state === "in") return "LIVE";
|
|
3123
|
-
return
|
|
3397
|
+
return void 0;
|
|
3124
3398
|
}
|
|
3125
3399
|
function parseMinute(st) {
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
if (
|
|
3130
|
-
|
|
3400
|
+
const s = st;
|
|
3401
|
+
if (s?.type?.state !== "in") return void 0;
|
|
3402
|
+
const dc = typeof s.displayClock === "string" ? s.displayClock.match(/(\d+)/) : null;
|
|
3403
|
+
if (dc) return count(Number.parseInt(dc[1], 10), MAX_MINUTE);
|
|
3404
|
+
if (typeof s.clock === "number" && s.clock > 0) {
|
|
3405
|
+
const n = Math.floor(s.clock / 60);
|
|
3406
|
+
return n > 0 ? count(n, MAX_MINUTE) : void 0;
|
|
3131
3407
|
}
|
|
3132
3408
|
return void 0;
|
|
3133
3409
|
}
|
|
@@ -3141,58 +3417,74 @@ var SLUG_TO_STAGE = {
|
|
|
3141
3417
|
final: "F"
|
|
3142
3418
|
};
|
|
3143
3419
|
function stageFromSlug(slug) {
|
|
3144
|
-
if (slug
|
|
3145
|
-
if (
|
|
3420
|
+
if (slug == null || slug === "") return "GROUP";
|
|
3421
|
+
if (typeof slug === "string" && Object.hasOwn(SLUG_TO_STAGE, slug)) {
|
|
3422
|
+
const mapped = SLUG_TO_STAGE[slug];
|
|
3423
|
+
if (mapped) return mapped;
|
|
3424
|
+
}
|
|
3146
3425
|
return "FRIENDLY";
|
|
3147
3426
|
}
|
|
3148
|
-
function
|
|
3149
|
-
if (
|
|
3150
|
-
|
|
3151
|
-
return
|
|
3152
|
-
}
|
|
3153
|
-
function
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
);
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
}
|
|
3164
|
-
|
|
3165
|
-
const
|
|
3166
|
-
const
|
|
3167
|
-
|
|
3168
|
-
const
|
|
3427
|
+
function toGoals(v) {
|
|
3428
|
+
if (typeof v === "number") return count(v, MAX_GOALS);
|
|
3429
|
+
if (typeof v !== "string" || v.trim() === "") return void 0;
|
|
3430
|
+
return /^-?\d{1,9}$/.test(v.trim()) ? count(Number(v.trim()), MAX_GOALS) : void 0;
|
|
3431
|
+
}
|
|
3432
|
+
function parseEspnEvent(raw, ctx = {}) {
|
|
3433
|
+
if (!raw || typeof raw !== "object") return malformed("event is not an object");
|
|
3434
|
+
const ev = raw;
|
|
3435
|
+
const id = opaqueId(ev.id, ESPN_ID);
|
|
3436
|
+
if (!id) return malformed("event id is absent or not an identifier");
|
|
3437
|
+
const kickoff = canonicalTimestamp(ev.date);
|
|
3438
|
+
if (!kickoff) return malformed("event date is absent or not one instant");
|
|
3439
|
+
const comp = Array.isArray(ev.competitions) ? ev.competitions[0] : void 0;
|
|
3440
|
+
const rawCompetitors = takeBounded(comp?.competitors, 4);
|
|
3441
|
+
if (rawCompetitors.length !== 2) {
|
|
3442
|
+
return definitiveNone(`expected 2 competitors, found ${rawCompetitors.length}`);
|
|
3443
|
+
}
|
|
3444
|
+
const homeRaw = rawCompetitors.find((c) => c?.homeAway === "home");
|
|
3445
|
+
const awayRaw = rawCompetitors.find((c) => c?.homeAway === "away");
|
|
3446
|
+
if (!homeRaw || !awayRaw) return definitiveNone("competitors do not state home and away");
|
|
3447
|
+
const homeP = toParticipant(homeRaw);
|
|
3448
|
+
const awayP = toParticipant(awayRaw);
|
|
3449
|
+
if (homeP.kind !== "valid") return homeP;
|
|
3450
|
+
if (awayP.kind !== "valid") return awayP;
|
|
3451
|
+
const h = homeP.value;
|
|
3452
|
+
const a = awayP.value;
|
|
3453
|
+
if (h.kind === "team" && a.kind === "team" && h.providerId === a.providerId) {
|
|
3454
|
+
return definitiveNone("both competitors are the same team");
|
|
3455
|
+
}
|
|
3456
|
+
const home = homeP.value.team;
|
|
3457
|
+
const away = awayP.value.team;
|
|
3169
3458
|
const status = mapStatus(ev.status ?? comp?.status);
|
|
3170
|
-
|
|
3171
|
-
const
|
|
3172
|
-
const away = toTeam(awayC?.team);
|
|
3459
|
+
if (!status) return malformed("event status is not a status we recognize");
|
|
3460
|
+
const stage = member(stageFromSlug(ev.season?.slug), STAGES2) ?? "FRIENDLY";
|
|
3173
3461
|
let group;
|
|
3174
3462
|
if (stage === "GROUP" && ctx.groupByTeam) {
|
|
3175
3463
|
group = ctx.groupByTeam[home.code] ?? ctx.groupByTeam[away.code];
|
|
3176
3464
|
}
|
|
3177
|
-
const hs =
|
|
3178
|
-
const as =
|
|
3179
|
-
const
|
|
3465
|
+
const hs = toGoals(homeRaw.score);
|
|
3466
|
+
const as = toGoals(awayRaw.score);
|
|
3467
|
+
const scoreExpected = status === "LIVE" || status === "HT" || status === "FT";
|
|
3468
|
+
const hasScore = scoreExpected && hs !== void 0 && as !== void 0;
|
|
3469
|
+
if (scoreExpected && !hasScore) return malformed("event score is absent or unreadable");
|
|
3470
|
+
const hShoot = toGoals(homeRaw.shootoutScore);
|
|
3471
|
+
const aShoot = toGoals(awayRaw.shootoutScore);
|
|
3472
|
+
const shootoutPresent = homeRaw.shootoutScore !== void 0 || awayRaw.shootoutScore !== void 0;
|
|
3473
|
+
const shootout = shootoutPresent ? { home: hShoot, away: aShoot } : void 0;
|
|
3180
3474
|
let winnerCode;
|
|
3181
3475
|
if (isFinished(status)) {
|
|
3182
|
-
|
|
3183
|
-
|
|
3476
|
+
const winners = [homeRaw, awayRaw].filter((c) => flag(c.winner) === true);
|
|
3477
|
+
if (winners.length === 1) winnerCode = winners[0] === homeRaw ? home.code : away.code;
|
|
3184
3478
|
}
|
|
3185
|
-
const
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
return {
|
|
3189
|
-
id: ev.id,
|
|
3479
|
+
const venue = comp?.venue;
|
|
3480
|
+
return sealMatch({
|
|
3481
|
+
id,
|
|
3190
3482
|
stage,
|
|
3191
3483
|
group,
|
|
3192
|
-
kickoff
|
|
3193
|
-
venue:
|
|
3194
|
-
city:
|
|
3195
|
-
country:
|
|
3484
|
+
kickoff,
|
|
3485
|
+
venue: venue?.fullName,
|
|
3486
|
+
city: venue?.address?.city,
|
|
3487
|
+
country: venue?.address?.country,
|
|
3196
3488
|
home,
|
|
3197
3489
|
away,
|
|
3198
3490
|
score: hasScore ? { home: hs, away: as } : void 0,
|
|
@@ -3200,57 +3492,229 @@ function mapEspnEvent(ev, ctx = {}) {
|
|
|
3200
3492
|
minute: parseMinute(ev.status ?? comp?.status),
|
|
3201
3493
|
status,
|
|
3202
3494
|
winnerCode,
|
|
3203
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3204
|
-
|
|
3495
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3496
|
+
events: ev.events
|
|
3497
|
+
});
|
|
3205
3498
|
}
|
|
3206
|
-
function
|
|
3207
|
-
|
|
3499
|
+
function parseEspnEvents(raw, ctx = {}) {
|
|
3500
|
+
const all = raw?.events;
|
|
3501
|
+
const readable = Array.isArray(all);
|
|
3502
|
+
const total = readable ? all.length : 0;
|
|
3503
|
+
const considered = takeBounded(all, MAX_EVENTS);
|
|
3504
|
+
const items = [];
|
|
3505
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
3506
|
+
let complete = readable && considered.length === total;
|
|
3507
|
+
for (const event of considered) {
|
|
3508
|
+
const parsed = parseEspnEvent(event, ctx);
|
|
3509
|
+
if (parsed.kind !== "valid") {
|
|
3510
|
+
if (parsed.kind !== "definitive-none") complete = false;
|
|
3511
|
+
continue;
|
|
3512
|
+
}
|
|
3513
|
+
if (seenIds.has(parsed.value.id)) {
|
|
3514
|
+
complete = false;
|
|
3515
|
+
continue;
|
|
3516
|
+
}
|
|
3517
|
+
seenIds.add(parsed.value.id);
|
|
3518
|
+
items.push(parsed.value);
|
|
3519
|
+
}
|
|
3520
|
+
return {
|
|
3521
|
+
items,
|
|
3522
|
+
total,
|
|
3523
|
+
shown: items.length,
|
|
3524
|
+
truncated: total > considered.length,
|
|
3525
|
+
// Some record was unreadable, or the window did not cover the payload, or
|
|
3526
|
+
// the envelope itself was not a list — none of those is a complete account
|
|
3527
|
+
// of what the provider sent.
|
|
3528
|
+
complete
|
|
3529
|
+
};
|
|
3208
3530
|
}
|
|
3209
|
-
function statVal(stats, name) {
|
|
3210
|
-
|
|
3211
|
-
|
|
3531
|
+
function statVal(stats, name, signed = false) {
|
|
3532
|
+
if (!Array.isArray(stats) || stats.length > 64) return void 0;
|
|
3533
|
+
const matches = takeBounded(stats, 64).filter(
|
|
3534
|
+
(s) => s?.name === name
|
|
3535
|
+
);
|
|
3536
|
+
if (matches.length !== 1) return void 0;
|
|
3537
|
+
const v = matches[0]?.value;
|
|
3538
|
+
if (typeof v !== "number" || !Number.isFinite(v) || !Number.isInteger(v)) return void 0;
|
|
3539
|
+
const limit = 1e3;
|
|
3540
|
+
return v > limit || v < (signed ? -limit : 0) ? void 0 : v;
|
|
3541
|
+
}
|
|
3542
|
+
function optionalStatVal(stats, name) {
|
|
3543
|
+
if (!Array.isArray(stats) || stats.length > 64) return void 0;
|
|
3544
|
+
const matches = takeBounded(stats, 64).filter(
|
|
3545
|
+
(s) => s?.name === name
|
|
3546
|
+
);
|
|
3547
|
+
if (matches.length === 0) return 0;
|
|
3548
|
+
if (matches.length !== 1) return void 0;
|
|
3549
|
+
const v = matches[0]?.value;
|
|
3550
|
+
return typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= 1e3 ? v : void 0;
|
|
3212
3551
|
}
|
|
3213
3552
|
function entryToRow(e) {
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3553
|
+
const names = teamNames(e?.team);
|
|
3554
|
+
if (names.length === 0) return definitiveNone("standings entry names no team");
|
|
3555
|
+
const name = names[0];
|
|
3556
|
+
const code = teamCode(e?.team?.abbreviation, name);
|
|
3557
|
+
const played = statVal(e.stats, "gamesPlayed");
|
|
3558
|
+
const won = statVal(e.stats, "wins");
|
|
3559
|
+
const drawn = statVal(e.stats, "ties");
|
|
3560
|
+
const lost = statVal(e.stats, "losses");
|
|
3561
|
+
const goalsFor = statVal(e.stats, "pointsFor");
|
|
3562
|
+
const goalsAgainst = statVal(e.stats, "pointsAgainst");
|
|
3563
|
+
const goalDiff = statVal(e.stats, "pointDifferential", true);
|
|
3564
|
+
const points = statVal(e.stats, "points", true);
|
|
3565
|
+
const deductions = optionalStatVal(e.stats, "deductions");
|
|
3566
|
+
const providerRank = statVal(e.stats, "rank");
|
|
3567
|
+
if (played === void 0 || won === void 0 || drawn === void 0 || lost === void 0 || goalsFor === void 0 || goalsAgainst === void 0 || goalDiff === void 0 || points === void 0 || deductions === void 0 || providerRank === void 0 || providerRank < 1) {
|
|
3568
|
+
return malformed("standings entry has missing or invalid statistics");
|
|
3569
|
+
}
|
|
3570
|
+
if (played !== won + drawn + lost) {
|
|
3571
|
+
return malformed("standings entry games do not add up");
|
|
3572
|
+
}
|
|
3573
|
+
if (goalDiff !== goalsFor - goalsAgainst) {
|
|
3574
|
+
return malformed("standings entry goal difference does not add up");
|
|
3575
|
+
}
|
|
3576
|
+
if (points !== won * 3 + drawn - deductions) {
|
|
3577
|
+
return malformed("standings entry points do not add up");
|
|
3578
|
+
}
|
|
3579
|
+
return valid({
|
|
3580
|
+
team: { code, name, flag: productFlag(name) },
|
|
3581
|
+
played,
|
|
3582
|
+
won,
|
|
3583
|
+
drawn,
|
|
3584
|
+
lost,
|
|
3585
|
+
goalsFor,
|
|
3586
|
+
goalsAgainst,
|
|
3587
|
+
goalDiff,
|
|
3588
|
+
points,
|
|
3589
|
+
providerId: opaqueId(e?.team?.id, ESPN_ID),
|
|
3590
|
+
providerRank
|
|
3591
|
+
});
|
|
3225
3592
|
}
|
|
3226
|
-
function
|
|
3593
|
+
function parseEspnStandings(raw) {
|
|
3594
|
+
const rawChildren = raw?.children;
|
|
3595
|
+
const readable = Array.isArray(rawChildren);
|
|
3596
|
+
const rawCount = readable ? rawChildren.length : 0;
|
|
3597
|
+
const children = takeBounded(rawChildren, MAX_GROUPS * 4);
|
|
3598
|
+
const sawAllChildren = rawCount === children.length;
|
|
3599
|
+
let rowsTruncated = false;
|
|
3600
|
+
let complete = readable && sawAllChildren;
|
|
3227
3601
|
const out = [];
|
|
3228
|
-
|
|
3229
|
-
|
|
3602
|
+
const seenGroups = /* @__PURE__ */ new Set();
|
|
3603
|
+
const seenProviderIds = /* @__PURE__ */ new Set();
|
|
3604
|
+
for (const child of children) {
|
|
3605
|
+
const label = humanLabel(child?.name ?? child?.abbreviation);
|
|
3606
|
+
const letter = label.match(/Group\s+([A-L])/i)?.[1]?.toUpperCase();
|
|
3230
3607
|
if (!letter) continue;
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
}
|
|
3608
|
+
if (seenGroups.has(letter)) {
|
|
3609
|
+
complete = false;
|
|
3610
|
+
continue;
|
|
3611
|
+
}
|
|
3612
|
+
seenGroups.add(letter);
|
|
3613
|
+
const rawEntries = child?.standings?.entries;
|
|
3614
|
+
if (!Array.isArray(rawEntries) || rawEntries.length === 0) {
|
|
3615
|
+
complete = false;
|
|
3616
|
+
continue;
|
|
3617
|
+
}
|
|
3618
|
+
if (rawEntries.length > MAX_GROUP_ROWS) {
|
|
3619
|
+
rowsTruncated = true;
|
|
3620
|
+
complete = false;
|
|
3621
|
+
}
|
|
3622
|
+
const entries = takeBounded(rawEntries, MAX_GROUP_ROWS);
|
|
3623
|
+
const seenTeams = /* @__PURE__ */ new Set();
|
|
3624
|
+
const seenRanks = /* @__PURE__ */ new Set();
|
|
3625
|
+
const ranked = [];
|
|
3626
|
+
for (const e of entries) {
|
|
3627
|
+
const r = entryToRow(e);
|
|
3628
|
+
if (r.kind !== "valid") {
|
|
3629
|
+
if (r.kind !== "definitive-none") complete = false;
|
|
3630
|
+
continue;
|
|
3631
|
+
}
|
|
3632
|
+
const key = r.value.providerId ?? r.value.team.code;
|
|
3633
|
+
if (seenTeams.has(key) || seenRanks.has(r.value.providerRank) || r.value.providerId !== void 0 && seenProviderIds.has(r.value.providerId)) {
|
|
3634
|
+
complete = false;
|
|
3635
|
+
continue;
|
|
3636
|
+
}
|
|
3637
|
+
seenTeams.add(key);
|
|
3638
|
+
seenRanks.add(r.value.providerRank);
|
|
3639
|
+
if (r.value.providerId !== void 0) seenProviderIds.add(r.value.providerId);
|
|
3640
|
+
const { providerId: _dropId, providerRank: rank, ...row } = r.value;
|
|
3641
|
+
ranked.push({ row, rank });
|
|
3642
|
+
}
|
|
3235
3643
|
ranked.sort((a, b) => {
|
|
3236
3644
|
if (a.rank && b.rank && a.rank !== b.rank) return a.rank - b.rank;
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
return
|
|
3645
|
+
if (b.row.points !== a.row.points) return b.row.points - a.row.points;
|
|
3646
|
+
if (b.row.goalDiff !== a.row.goalDiff) return b.row.goalDiff - a.row.goalDiff;
|
|
3647
|
+
return b.row.goalsFor - a.row.goalsFor;
|
|
3240
3648
|
});
|
|
3649
|
+
if (ranked.length === 0) {
|
|
3650
|
+
complete = false;
|
|
3651
|
+
continue;
|
|
3652
|
+
}
|
|
3241
3653
|
out.push({ group: letter, rows: ranked.map((x) => x.row) });
|
|
3242
3654
|
}
|
|
3243
|
-
|
|
3244
|
-
|
|
3655
|
+
return {
|
|
3656
|
+
items: out,
|
|
3657
|
+
total: seenGroups.size,
|
|
3658
|
+
shown: out.length,
|
|
3659
|
+
// We stopped early if the child list or any single group's rows were cut.
|
|
3660
|
+
truncated: !sawAllChildren || rowsTruncated,
|
|
3661
|
+
complete: complete && !rowsTruncated
|
|
3662
|
+
};
|
|
3663
|
+
}
|
|
3664
|
+
|
|
3665
|
+
// src/adapters/espn.ts
|
|
3666
|
+
var ESPN_SOCCER = "https://site.api.espn.com/apis/site/v2/sports/soccer";
|
|
3667
|
+
var DEFAULT_COMPETITION = "fifa.world";
|
|
3668
|
+
var DEFAULT_BASE = `${ESPN_SOCCER}/${DEFAULT_COMPETITION}`;
|
|
3669
|
+
var USER_AGENT = `claudinho/${"0.9.4"} (+https://github.com/arturogarrido/claudinho)`;
|
|
3670
|
+
var MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
3671
|
+
function competitionBase(slug) {
|
|
3672
|
+
return `${ESPN_SOCCER}/${slug}`;
|
|
3673
|
+
}
|
|
3674
|
+
var DEFAULT_TIMEOUT_MS = 6e3;
|
|
3675
|
+
var STANDINGS_SHARE_MS = 3e4;
|
|
3676
|
+
var ProviderError = class extends Error {
|
|
3677
|
+
kind;
|
|
3678
|
+
status;
|
|
3679
|
+
constructor(message, kind, status) {
|
|
3680
|
+
super(message);
|
|
3681
|
+
this.name = "ProviderError";
|
|
3682
|
+
this.kind = kind;
|
|
3683
|
+
this.status = status;
|
|
3684
|
+
}
|
|
3685
|
+
/** 429/403 — the upstream is refusing us; retrying at the live cadence makes it worse. */
|
|
3686
|
+
get throttled() {
|
|
3687
|
+
return this.kind === "http" && (this.status === 429 || this.status === 403);
|
|
3688
|
+
}
|
|
3689
|
+
};
|
|
3690
|
+
function toEspnDate(d) {
|
|
3691
|
+
return d.replace(/\D/g, "").slice(0, 8);
|
|
3692
|
+
}
|
|
3693
|
+
function mapEspnEvent(ev, ctx = {}) {
|
|
3694
|
+
return parsedValue(parseEspnEvent(ev, ctx));
|
|
3695
|
+
}
|
|
3696
|
+
function parseStandings(data) {
|
|
3697
|
+
return [...parseEspnStandings(data).items];
|
|
3698
|
+
}
|
|
3699
|
+
function usableProviderItems(kind, parsed, hasUsableRecord = parsed.items.length > 0) {
|
|
3700
|
+
if (!hasUsableRecord && (!parsed.complete || parsed.total > 0)) {
|
|
3701
|
+
throw new ProviderError(`ESPN ${kind} payload had no readable records`, "parse");
|
|
3702
|
+
}
|
|
3703
|
+
return [...parsed.items];
|
|
3245
3704
|
}
|
|
3246
3705
|
var EspnAdapter = class {
|
|
3247
3706
|
constructor(opts = {}) {
|
|
3248
3707
|
this.opts = opts;
|
|
3708
|
+
const expected = opts.expectedStandingsGroups ?? (opts.baseUrl === void 0 ? groups() : void 0);
|
|
3709
|
+
this.expectedStandingsGroups = expected ? [...expected] : void 0;
|
|
3710
|
+
this.standingsFallbackGroups = opts.baseUrl === void 0 && expected ? [...expected] : void 0;
|
|
3249
3711
|
}
|
|
3250
3712
|
opts;
|
|
3251
3713
|
name = "espn";
|
|
3252
3714
|
capabilities = { push: false, latencyHintSec: 45 };
|
|
3253
|
-
|
|
3715
|
+
expectedStandingsGroups;
|
|
3716
|
+
standingsFallbackGroups;
|
|
3717
|
+
/** Short-lived team-code -> group-letter map (built lazily from standings). */
|
|
3254
3718
|
groupMap;
|
|
3255
3719
|
/**
|
|
3256
3720
|
* One in-flight/recent standings fetch shared by fetchStandings and
|
|
@@ -3293,38 +3757,48 @@ var EspnAdapter = class {
|
|
|
3293
3757
|
if (this.standingsShared && now - this.standingsShared.at < STANDINGS_SHARE_MS) {
|
|
3294
3758
|
return this.standingsShared.promise;
|
|
3295
3759
|
}
|
|
3296
|
-
const promise = this.get(this.standingsUrl()).then(
|
|
3297
|
-
|
|
3298
|
-
|
|
3760
|
+
const promise = this.get(this.standingsUrl()).then((d) => {
|
|
3761
|
+
const parsed = parseEspnStandings(d);
|
|
3762
|
+
return usableProviderItems(
|
|
3763
|
+
"standings",
|
|
3764
|
+
parsed,
|
|
3765
|
+
parsed.items.some((table) => table.rows.length > 0)
|
|
3766
|
+
);
|
|
3767
|
+
});
|
|
3299
3768
|
this.standingsShared = { at: now, promise };
|
|
3300
|
-
promise.catch(() => {
|
|
3769
|
+
void promise.catch(() => {
|
|
3301
3770
|
if (this.standingsShared?.promise === promise) this.standingsShared = void 0;
|
|
3302
3771
|
});
|
|
3303
3772
|
return promise;
|
|
3304
3773
|
}
|
|
3305
3774
|
/**
|
|
3306
3775
|
* Authoritative, cumulative group tables from the standings endpoint. Throws
|
|
3307
|
-
* on fetch
|
|
3308
|
-
*
|
|
3776
|
+
* on fetch failure. Group-stage only: non-group `children` are filtered out
|
|
3777
|
+
* by {@link parseStandings}; malformed rows are omitted without hiding their
|
|
3778
|
+
* readable siblings.
|
|
3309
3779
|
*/
|
|
3310
3780
|
async fetchStandings() {
|
|
3311
3781
|
return this.sharedStandings();
|
|
3312
3782
|
}
|
|
3313
3783
|
/**
|
|
3314
|
-
* Build (and cache) a team-code -> group-letter map from the standings
|
|
3784
|
+
* Build (and briefly cache) a team-code -> group-letter map from the standings
|
|
3315
3785
|
* endpoint. Best-effort: returns {} if standings are unavailable — but a
|
|
3316
|
-
* transient failure is NOT cached
|
|
3317
|
-
*
|
|
3786
|
+
* transient failure is NOT cached, and a partial successful parse expires at
|
|
3787
|
+
* the standings TTL, so neither can silently drop group letters for the
|
|
3788
|
+
* adapter's lifetime.
|
|
3318
3789
|
* Reuses the same parse/fetch as {@link fetchStandings}, so the two never
|
|
3319
3790
|
* drift and one command never fetches standings twice.
|
|
3320
3791
|
*/
|
|
3321
3792
|
async fetchGroupMap(force = false) {
|
|
3322
|
-
|
|
3793
|
+
const now = Date.now();
|
|
3794
|
+
if (!force && this.groupMap && now - this.groupMap.at < STANDINGS_SHARE_MS) {
|
|
3795
|
+
return this.groupMap.value;
|
|
3796
|
+
}
|
|
3323
3797
|
try {
|
|
3324
3798
|
const tables = await this.sharedStandings();
|
|
3325
3799
|
const map = {};
|
|
3326
3800
|
for (const t2 of tables) for (const r of t2.rows) map[r.team.code] = t2.group;
|
|
3327
|
-
this.groupMap = map;
|
|
3801
|
+
this.groupMap = { at: Date.now(), value: map };
|
|
3328
3802
|
return map;
|
|
3329
3803
|
} catch {
|
|
3330
3804
|
return {};
|
|
@@ -3339,7 +3813,8 @@ var EspnAdapter = class {
|
|
|
3339
3813
|
this.opts.enrichGroups === false ? Promise.resolve({}) : this.fetchGroupMap(),
|
|
3340
3814
|
this.get(url.toString())
|
|
3341
3815
|
]);
|
|
3342
|
-
|
|
3816
|
+
const parsed = parseEspnEvents(data, { groupByTeam });
|
|
3817
|
+
return usableProviderItems("scoreboard", parsed);
|
|
3343
3818
|
}
|
|
3344
3819
|
async get(url) {
|
|
3345
3820
|
const doFetch = this.opts.fetchImpl ?? fetch;
|
|
@@ -3531,17 +4006,17 @@ function validateWinnerChain(nodes, problems) {
|
|
|
3531
4006
|
{ from: "QF", to: "SF", count: 4 },
|
|
3532
4007
|
{ from: "SF", to: "F", count: 2 }
|
|
3533
4008
|
];
|
|
3534
|
-
for (const { from, to, count } of chains) {
|
|
4009
|
+
for (const { from, to, count: count2 } of chains) {
|
|
3535
4010
|
const refs = nodes.filter((n) => n.stage === to).flatMap((n) => [n.home, n.away]).filter((r) => r.kind === "winner" && r.stage === from);
|
|
3536
4011
|
const indices = refs.map((r) => r.index);
|
|
3537
|
-
if (indices.length !==
|
|
3538
|
-
problems.push(`${to}: expected ${
|
|
4012
|
+
if (indices.length !== count2) {
|
|
4013
|
+
problems.push(`${to}: expected ${count2} ${from} winner refs, got ${indices.length}`);
|
|
3539
4014
|
}
|
|
3540
4015
|
const unique = new Set(indices);
|
|
3541
|
-
if (unique.size !==
|
|
3542
|
-
problems.push(`${to}: ${from} winner indices must be unique 1..${
|
|
4016
|
+
if (unique.size !== count2) {
|
|
4017
|
+
problems.push(`${to}: ${from} winner indices must be unique 1..${count2}`);
|
|
3543
4018
|
}
|
|
3544
|
-
for (let i = 1; i <=
|
|
4019
|
+
for (let i = 1; i <= count2; i++) {
|
|
3545
4020
|
if (!unique.has(i)) problems.push(`${to}: missing ${from} winner index ${i}`);
|
|
3546
4021
|
if (!indexMap.has(matchKey(from, i))) problems.push(`missing ${from} match index ${i}`);
|
|
3547
4022
|
}
|
|
@@ -4319,17 +4794,26 @@ async function getMatchesForDate(adapter, dateISO) {
|
|
|
4319
4794
|
}
|
|
4320
4795
|
async function getStandings(adapter, group) {
|
|
4321
4796
|
const want = group?.toUpperCase();
|
|
4797
|
+
const expected = adapter.expectedStandingsGroups;
|
|
4798
|
+
if (want && expected && !expected.includes(want)) {
|
|
4799
|
+
return { tables: [], degraded: false };
|
|
4800
|
+
}
|
|
4322
4801
|
if (adapter.fetchStandings) {
|
|
4323
4802
|
try {
|
|
4324
4803
|
const all = await adapter.fetchStandings();
|
|
4325
4804
|
const tables2 = (want ? all.filter((t2) => t2.group === want) : all).sort(
|
|
4326
4805
|
(a, b) => a.group.localeCompare(b.group)
|
|
4327
4806
|
);
|
|
4328
|
-
|
|
4807
|
+
const availableGroups = new Set(tables2.map((table) => table.group));
|
|
4808
|
+
const expectedGroupWasOmitted = want ? (expected?.includes(want) ?? false) && tables2.length === 0 : expected?.some((group2) => !availableGroups.has(group2)) ?? false;
|
|
4809
|
+
if (!expectedGroupWasOmitted) {
|
|
4810
|
+
return { tables: tables2, degraded: false, source: adapter.name };
|
|
4811
|
+
}
|
|
4329
4812
|
} catch {
|
|
4330
4813
|
}
|
|
4331
4814
|
}
|
|
4332
|
-
const
|
|
4815
|
+
const fallbackGroups = adapter.standingsFallbackGroups;
|
|
4816
|
+
const letters = fallbackGroups ? want ? fallbackGroups.includes(want) ? [want] : [] : [...new Set(fallbackGroups)].sort((a, b) => a.localeCompare(b)) : [];
|
|
4333
4817
|
const tables = letters.map((g) => ({ group: g, rows: rosterAtZero(fixturesByGroup(g)) })).filter((t2) => t2.rows.length > 0);
|
|
4334
4818
|
return { tables, degraded: true };
|
|
4335
4819
|
}
|
|
@@ -4386,7 +4870,10 @@ async function marketFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Dat
|
|
|
4386
4870
|
try {
|
|
4387
4871
|
const win = knockoutWindow();
|
|
4388
4872
|
if (adapter.fetchWindow && win) {
|
|
4389
|
-
fixtures = mergeLive(
|
|
4873
|
+
fixtures = mergeLive(
|
|
4874
|
+
fixtures,
|
|
4875
|
+
await adapter.fetchWindow(win.start, win.end)
|
|
4876
|
+
);
|
|
4390
4877
|
}
|
|
4391
4878
|
} catch {
|
|
4392
4879
|
overlayFailed = true;
|
|
@@ -4450,15 +4937,149 @@ async function getMatchById(adapter, id) {
|
|
|
4450
4937
|
async function getLiveMatches(adapter, now = /* @__PURE__ */ new Date()) {
|
|
4451
4938
|
try {
|
|
4452
4939
|
const day = now.toISOString().slice(0, 10);
|
|
4453
|
-
const matches = adapter.fetchWindow ?
|
|
4454
|
-
(m) => isLive(m.status)
|
|
4455
|
-
) : await adapter.fetchLive();
|
|
4940
|
+
const matches = (adapter.fetchWindow ? await adapter.fetchWindow(shiftUtcDate(day, -1), shiftUtcDate(day, 1)) : await adapter.fetchLive()).filter((m) => isLive(m.status));
|
|
4456
4941
|
return { matches, degraded: false, source: adapter.name };
|
|
4457
4942
|
} catch {
|
|
4458
4943
|
return { matches: [], degraded: true };
|
|
4459
4944
|
}
|
|
4460
4945
|
}
|
|
4461
4946
|
|
|
4947
|
+
// src/markets/format.ts
|
|
4948
|
+
function pct(p) {
|
|
4949
|
+
return Math.round(p * 100);
|
|
4950
|
+
}
|
|
4951
|
+
var KNOWN_MARKET_SOURCES = ["polymarket", "fake"];
|
|
4952
|
+
function marketSourceLabel(source) {
|
|
4953
|
+
if (source === "polymarket") return "Polymarket";
|
|
4954
|
+
if (source === "fake") return "demo data";
|
|
4955
|
+
return source.charAt(0).toUpperCase() + source.slice(1);
|
|
4956
|
+
}
|
|
4957
|
+
function outcomeLabel(o, match) {
|
|
4958
|
+
if (o.kind === "home") return match.home.name;
|
|
4959
|
+
if (o.kind === "away") return match.away.name;
|
|
4960
|
+
if (o.kind === "draw") return "Draw";
|
|
4961
|
+
return o.label;
|
|
4962
|
+
}
|
|
4963
|
+
function utcHhmm(iso) {
|
|
4964
|
+
const t2 = Date.parse(iso);
|
|
4965
|
+
if (!Number.isFinite(t2)) return "";
|
|
4966
|
+
return `${new Date(t2).toISOString().slice(11, 16)} UTC`;
|
|
4967
|
+
}
|
|
4968
|
+
function marketFavoriteText(signal, match) {
|
|
4969
|
+
const fav = signal.favorite;
|
|
4970
|
+
if (!fav || fav.strength === "close") return "Prediction markets see this match as close.";
|
|
4971
|
+
if (fav.kind === "draw") return "Prediction markets see a draw as the top outcome.";
|
|
4972
|
+
const name = fav.kind === "home" ? match.home.name : match.away.name;
|
|
4973
|
+
return fav.strength === "clear" ? `Prediction markets favor ${name}.` : `Prediction markets slightly favor ${name}.`;
|
|
4974
|
+
}
|
|
4975
|
+
function marketProbabilityText(signal, match) {
|
|
4976
|
+
const order = ["home", "draw", "away"];
|
|
4977
|
+
const parts = [];
|
|
4978
|
+
for (const kind of order) {
|
|
4979
|
+
const o = signal.outcomes.find((x) => x.kind === kind);
|
|
4980
|
+
if (o) parts.push(`${outcomeLabel(o, match)} ${pct(o.probability)}%`);
|
|
4981
|
+
}
|
|
4982
|
+
for (const o of signal.outcomes) {
|
|
4983
|
+
if (o.kind === "other") parts.push(`${outcomeLabel(o, match)} ${pct(o.probability)}%`);
|
|
4984
|
+
}
|
|
4985
|
+
return parts.join(" \xB7 ");
|
|
4986
|
+
}
|
|
4987
|
+
function marketAttributionText(signal) {
|
|
4988
|
+
const time = utcHhmm(signal.asOf);
|
|
4989
|
+
const src = `Source: ${marketSourceLabel(signal.source)}`;
|
|
4990
|
+
return time ? `${src} \xB7 updated ${time}` : src;
|
|
4991
|
+
}
|
|
4992
|
+
function marketLine(signal, match) {
|
|
4993
|
+
return `Market: ${marketProbabilityText(signal, match)} \xB7 ${marketSourceLabel(
|
|
4994
|
+
signal.source
|
|
4995
|
+
)} \xB7 informational only`;
|
|
4996
|
+
}
|
|
4997
|
+
function marketBlock(signal, match) {
|
|
4998
|
+
const lines = [];
|
|
4999
|
+
if (signal.stale) lines.push("Market signal is stale; the reading may be out of date.");
|
|
5000
|
+
lines.push(marketFavoriteText(signal, match));
|
|
5001
|
+
lines.push(marketProbabilityText(signal, match));
|
|
5002
|
+
lines.push(`${marketAttributionText(signal)} \xB7 informational only`);
|
|
5003
|
+
return lines;
|
|
5004
|
+
}
|
|
5005
|
+
|
|
5006
|
+
// src/trust/market.ts
|
|
5007
|
+
var MAX_OUTCOMES = 128;
|
|
5008
|
+
var MATCH_ID = /^[0-9]{1,20}$/;
|
|
5009
|
+
var MARKET_ID = /^(?:[0-9]{1,32}|fifwc-[a-z]{2,3}-[a-z]{2,3}-\d{4}-\d{2}-\d{2})$/;
|
|
5010
|
+
var OUTCOME_KINDS = /* @__PURE__ */ new Set(["home", "draw", "away", "other"]);
|
|
5011
|
+
var TEAM_CODE_COLUMNS2 = 8;
|
|
5012
|
+
function sealOutcome(raw) {
|
|
5013
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
5014
|
+
const o = raw;
|
|
5015
|
+
const kind = member(o.kind, OUTCOME_KINDS);
|
|
5016
|
+
const p = probability(o.probability);
|
|
5017
|
+
if (!kind || p === void 0) return void 0;
|
|
5018
|
+
const out = { kind };
|
|
5019
|
+
if (o.teamCode !== void 0) {
|
|
5020
|
+
if (typeof o.teamCode !== "string") return void 0;
|
|
5021
|
+
out.teamCode = humanLabel(o.teamCode, TEAM_CODE_COLUMNS2);
|
|
5022
|
+
}
|
|
5023
|
+
out.label = humanLabel(o.label);
|
|
5024
|
+
out.probability = p;
|
|
5025
|
+
if ((out.kind === "home" || out.kind === "away") && !out.teamCode) return void 0;
|
|
5026
|
+
return out;
|
|
5027
|
+
}
|
|
5028
|
+
function hasDuplicateKind(outcomes) {
|
|
5029
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5030
|
+
for (const o of outcomes) {
|
|
5031
|
+
if (o.kind === "other") continue;
|
|
5032
|
+
if (seen.has(o.kind)) return true;
|
|
5033
|
+
seen.add(o.kind);
|
|
5034
|
+
}
|
|
5035
|
+
return false;
|
|
5036
|
+
}
|
|
5037
|
+
function sealMarketSignal(raw, options = {}) {
|
|
5038
|
+
if (!raw || typeof raw !== "object") return malformed("signal is not an object");
|
|
5039
|
+
const s = raw;
|
|
5040
|
+
const matchId = opaqueId(s.matchId, MATCH_ID);
|
|
5041
|
+
if (!matchId) return malformed("signal names no fixture");
|
|
5042
|
+
if (!Array.isArray(s.outcomes) || s.outcomes.length > MAX_OUTCOMES) {
|
|
5043
|
+
return malformed("signal outcomes are absent or exceed the cap");
|
|
5044
|
+
}
|
|
5045
|
+
const outcomes = [];
|
|
5046
|
+
for (const rawOutcome of takeBounded(s.outcomes, MAX_OUTCOMES)) {
|
|
5047
|
+
const outcome = sealOutcome(rawOutcome);
|
|
5048
|
+
if (!outcome) return malformed("signal carries an unreadable outcome");
|
|
5049
|
+
outcomes.push(outcome);
|
|
5050
|
+
}
|
|
5051
|
+
if (hasDuplicateKind(outcomes)) {
|
|
5052
|
+
return ambiguous("two outcomes claim the same result");
|
|
5053
|
+
}
|
|
5054
|
+
const sourceMarketId = opaqueId(s.sourceMarketId, MARKET_ID);
|
|
5055
|
+
const liquidity = quantity(s.liquidity);
|
|
5056
|
+
const volume24h = quantity(s.volume24h);
|
|
5057
|
+
const out = {
|
|
5058
|
+
matchId,
|
|
5059
|
+
// Allow-listed, not merely stripped: this lands in the provider-attribution
|
|
5060
|
+
// slot, where `marketSourceLabel` falls through to the raw string for an
|
|
5061
|
+
// unrecognized provider — attacker prose where the reader expects
|
|
5062
|
+
// "Polymarket".
|
|
5063
|
+
source: member(s.source, new Set(KNOWN_MARKET_SOURCES)) ?? ""
|
|
5064
|
+
};
|
|
5065
|
+
if (sourceMarketId) out.sourceMarketId = sourceMarketId;
|
|
5066
|
+
out.asOf = canonicalTimestamp(s.asOf) ?? "";
|
|
5067
|
+
out.fetchedAt = canonicalTimestamp(s.fetchedAt) ?? "";
|
|
5068
|
+
out.outcomes = outcomes;
|
|
5069
|
+
const isAmbiguous = s.ambiguous !== false;
|
|
5070
|
+
const favorite = isAmbiguous ? void 0 : deriveFavorite(outcomes);
|
|
5071
|
+
if (favorite) out.favorite = favorite;
|
|
5072
|
+
if (liquidity !== void 0) out.liquidity = liquidity;
|
|
5073
|
+
if (volume24h !== void 0) out.volume24h = volume24h;
|
|
5074
|
+
out.stale = s.stale !== false;
|
|
5075
|
+
out.ambiguous = isAmbiguous || out.source === "";
|
|
5076
|
+
out.stale = out.stale || isStaleSignal(out, { now: options.now, maxAgeMs: options.maxAgeMs });
|
|
5077
|
+
return valid(out);
|
|
5078
|
+
}
|
|
5079
|
+
function parseCachedMarketSignal(raw, options = {}) {
|
|
5080
|
+
return sealMarketSignal(raw, options);
|
|
5081
|
+
}
|
|
5082
|
+
|
|
4462
5083
|
// src/markets/normalize.ts
|
|
4463
5084
|
var DEFAULT_MAX_AGE_MS = 15 * 6e4;
|
|
4464
5085
|
function marketRelevant(match, now = /* @__PURE__ */ new Date()) {
|
|
@@ -4478,9 +5099,9 @@ function normalizeOutcomes(outcomes) {
|
|
|
4478
5099
|
probability: Number.isFinite(o.probability) && o.probability > 0 ? o.probability / sum : 0
|
|
4479
5100
|
}));
|
|
4480
5101
|
}
|
|
4481
|
-
function favoriteStrength(
|
|
4482
|
-
if (
|
|
4483
|
-
if (
|
|
5102
|
+
function favoriteStrength(probability2) {
|
|
5103
|
+
if (probability2 >= 0.65) return "clear";
|
|
5104
|
+
if (probability2 >= 0.52) return "slight";
|
|
4484
5105
|
return "close";
|
|
4485
5106
|
}
|
|
4486
5107
|
function deriveFavorite(outcomes) {
|
|
@@ -4499,14 +5120,16 @@ function deriveFavorite(outcomes) {
|
|
|
4499
5120
|
}
|
|
4500
5121
|
function mapsCleanly(match, outcomes) {
|
|
4501
5122
|
if (outcomes.some((o) => o.kind === "other")) return false;
|
|
5123
|
+
const kinds = outcomes.map((o) => o.kind);
|
|
5124
|
+
if (new Set(kinds).size !== kinds.length) return false;
|
|
4502
5125
|
const home = outcomes.find((o) => o.kind === "home");
|
|
4503
5126
|
const away = outcomes.find((o) => o.kind === "away");
|
|
4504
5127
|
const draw = outcomes.find((o) => o.kind === "draw");
|
|
4505
5128
|
if (!home || !away) return false;
|
|
4506
|
-
if (home.teamCode
|
|
5129
|
+
if (!home.teamCode || home.teamCode.toUpperCase() !== match.home.code.toUpperCase()) {
|
|
4507
5130
|
return false;
|
|
4508
5131
|
}
|
|
4509
|
-
if (away.teamCode
|
|
5132
|
+
if (!away.teamCode || away.teamCode.toUpperCase() !== match.away.code.toUpperCase()) {
|
|
4510
5133
|
return false;
|
|
4511
5134
|
}
|
|
4512
5135
|
if (match.stage === "GROUP" && !draw) return false;
|
|
@@ -4521,11 +5144,13 @@ function hasSaneDistribution(outcomes) {
|
|
|
4521
5144
|
const sum = priced.reduce((s, o) => s + o.probability, 0);
|
|
4522
5145
|
return sum > 0.97 && sum < 1.03;
|
|
4523
5146
|
}
|
|
5147
|
+
var FUTURE_SKEW_MS = 6e4;
|
|
4524
5148
|
function isStaleSignal(signal, options = {}) {
|
|
4525
5149
|
const maxAge = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
4526
5150
|
const asOf = Date.parse(signal.asOf);
|
|
4527
5151
|
if (!Number.isFinite(asOf)) return true;
|
|
4528
5152
|
const now = (options.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
5153
|
+
if (asOf - now > FUTURE_SKEW_MS) return true;
|
|
4529
5154
|
return now - asOf > maxAge;
|
|
4530
5155
|
}
|
|
4531
5156
|
function isReliableMarketSignal(signal, options = {}) {
|
|
@@ -4541,8 +5166,8 @@ function isReliableMarketSignal(signal, options = {}) {
|
|
|
4541
5166
|
}
|
|
4542
5167
|
function buildMarketSignal(input) {
|
|
4543
5168
|
const outcomes = normalizeOutcomes(input.outcomes);
|
|
4544
|
-
const
|
|
4545
|
-
const favorite =
|
|
5169
|
+
const ambiguous2 = input.ambiguous === true || !mapsCleanly(input.match, outcomes);
|
|
5170
|
+
const favorite = ambiguous2 ? void 0 : deriveFavorite(outcomes);
|
|
4546
5171
|
const signal = {
|
|
4547
5172
|
matchId: input.match.id,
|
|
4548
5173
|
source: input.source,
|
|
@@ -4554,68 +5179,35 @@ function buildMarketSignal(input) {
|
|
|
4554
5179
|
liquidity: input.liquidity,
|
|
4555
5180
|
volume24h: input.volume24h,
|
|
4556
5181
|
stale: false,
|
|
4557
|
-
ambiguous
|
|
5182
|
+
ambiguous: ambiguous2
|
|
4558
5183
|
};
|
|
4559
5184
|
signal.stale = isStaleSignal(signal, { now: input.now, maxAgeMs: input.maxAgeMs });
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
// src/markets/format.ts
|
|
4564
|
-
function pct(p) {
|
|
4565
|
-
return Math.round(p * 100);
|
|
4566
|
-
}
|
|
4567
|
-
function marketSourceLabel(source) {
|
|
4568
|
-
if (source === "polymarket") return "Polymarket";
|
|
4569
|
-
if (source === "fake") return "demo data";
|
|
4570
|
-
return source.charAt(0).toUpperCase() + source.slice(1);
|
|
4571
|
-
}
|
|
4572
|
-
function outcomeLabel(o, match) {
|
|
4573
|
-
if (o.kind === "home") return match.home.name;
|
|
4574
|
-
if (o.kind === "away") return match.away.name;
|
|
4575
|
-
if (o.kind === "draw") return "Draw";
|
|
4576
|
-
return o.label;
|
|
4577
|
-
}
|
|
4578
|
-
function utcHhmm(iso) {
|
|
4579
|
-
const t2 = Date.parse(iso);
|
|
4580
|
-
if (!Number.isFinite(t2)) return "";
|
|
4581
|
-
return `${new Date(t2).toISOString().slice(11, 16)} UTC`;
|
|
4582
|
-
}
|
|
4583
|
-
function marketFavoriteText(signal, match) {
|
|
4584
|
-
const fav = signal.favorite;
|
|
4585
|
-
if (!fav || fav.strength === "close") return "Prediction markets see this match as close.";
|
|
4586
|
-
if (fav.kind === "draw") return "Prediction markets see a draw as the top outcome.";
|
|
4587
|
-
const name = fav.kind === "home" ? match.home.name : match.away.name;
|
|
4588
|
-
return fav.strength === "clear" ? `Prediction markets favor ${name}.` : `Prediction markets slightly favor ${name}.`;
|
|
4589
|
-
}
|
|
4590
|
-
function marketProbabilityText(signal, match) {
|
|
4591
|
-
const order = ["home", "draw", "away"];
|
|
4592
|
-
const parts = [];
|
|
4593
|
-
for (const kind of order) {
|
|
4594
|
-
const o = signal.outcomes.find((x) => x.kind === kind);
|
|
4595
|
-
if (o) parts.push(`${outcomeLabel(o, match)} ${pct(o.probability)}%`);
|
|
5185
|
+
const sealed = sealMarketSignal(signal, { now: input.now, maxAgeMs: input.maxAgeMs });
|
|
5186
|
+
if (sealed.kind !== "valid") {
|
|
5187
|
+
return { ...signal, outcomes: [], favorite: void 0, stale: true, ambiguous: true };
|
|
4596
5188
|
}
|
|
4597
|
-
|
|
4598
|
-
if (o.kind === "other") parts.push(`${outcomeLabel(o, match)} ${pct(o.probability)}%`);
|
|
4599
|
-
}
|
|
4600
|
-
return parts.join(" \xB7 ");
|
|
5189
|
+
return { ...sealed.value, ambiguous: sealed.value.ambiguous || ambiguous2 };
|
|
4601
5190
|
}
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
5191
|
+
|
|
5192
|
+
// src/trust/batch.ts
|
|
5193
|
+
var NONE = { kind: "none" };
|
|
5194
|
+
function selectOne(candidates) {
|
|
5195
|
+
if (candidates.length === 1) return { kind: "one", value: candidates[0] };
|
|
5196
|
+
if (candidates.length === 0) return NONE;
|
|
5197
|
+
return { kind: "ambiguous", count: candidates.length };
|
|
5198
|
+
}
|
|
5199
|
+
function resolvedValues(batch) {
|
|
5200
|
+
const out = /* @__PURE__ */ new Map();
|
|
5201
|
+
for (const [key, r] of batch.results) if (r.kind === "valid") out.set(key, r.value);
|
|
5202
|
+
return out;
|
|
4606
5203
|
}
|
|
4607
|
-
function
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
5204
|
+
function cacheableKeys(batch) {
|
|
5205
|
+
const out = /* @__PURE__ */ new Set();
|
|
5206
|
+
for (const [key, r] of batch.results) if (isCacheable(r)) out.add(key);
|
|
5207
|
+
return out;
|
|
4611
5208
|
}
|
|
4612
|
-
function
|
|
4613
|
-
|
|
4614
|
-
if (signal.stale) lines.push("Market signal is stale; the reading may be out of date.");
|
|
4615
|
-
lines.push(marketFavoriteText(signal, match));
|
|
4616
|
-
lines.push(marketProbabilityText(signal, match));
|
|
4617
|
-
lines.push(`${marketAttributionText(signal)} \xB7 informational only`);
|
|
4618
|
-
return lines;
|
|
5209
|
+
function emptyBatch() {
|
|
5210
|
+
return { results: /* @__PURE__ */ new Map(), complete: false };
|
|
4619
5211
|
}
|
|
4620
5212
|
|
|
4621
5213
|
// src/markets/fake.ts
|
|
@@ -4632,14 +5224,12 @@ var FakeMarketProvider = class {
|
|
|
4632
5224
|
return void 0;
|
|
4633
5225
|
}
|
|
4634
5226
|
async findSignals(matches, options) {
|
|
4635
|
-
const
|
|
4636
|
-
const checked = /* @__PURE__ */ new Set();
|
|
5227
|
+
const results = /* @__PURE__ */ new Map();
|
|
4637
5228
|
for (const m of matches) {
|
|
4638
|
-
checked.add(m.id);
|
|
4639
5229
|
const s = await this.findSignal(m, options);
|
|
4640
|
-
|
|
5230
|
+
results.set(m.id, s ? valid(s) : definitiveNone("fake provider has no signal"));
|
|
4641
5231
|
}
|
|
4642
|
-
return {
|
|
5232
|
+
return { results, complete: true };
|
|
4643
5233
|
}
|
|
4644
5234
|
synthesize(match, options) {
|
|
4645
5235
|
const seed = hash(`${match.home.code}-${match.away.code}`);
|
|
@@ -4656,7 +5246,9 @@ var FakeMarketProvider = class {
|
|
|
4656
5246
|
return buildMarketSignal({
|
|
4657
5247
|
match,
|
|
4658
5248
|
source: "fake",
|
|
4659
|
-
|
|
5249
|
+
// Must satisfy the boundary's opaque-id grammar, like a real one:
|
|
5250
|
+
// a source id that only the live path accepts is the asymmetry itself.
|
|
5251
|
+
sourceMarketId: match.id,
|
|
4660
5252
|
asOf,
|
|
4661
5253
|
fetchedAt: now.toISOString(),
|
|
4662
5254
|
outcomes,
|
|
@@ -4684,6 +5276,8 @@ var DEFAULT_BASE2 = "https://gamma-api.polymarket.com";
|
|
|
4684
5276
|
var ALLOWED_HOSTS = /* @__PURE__ */ new Set(["gamma-api.polymarket.com"]);
|
|
4685
5277
|
var USER_AGENT2 = "claudinho/0.0 (+https://github.com/arturogarrido/claudinho)";
|
|
4686
5278
|
var DEFAULT_TIMEOUT_MS2 = 8e3;
|
|
5279
|
+
var MAX_EVENT_MARKETS = 256;
|
|
5280
|
+
var DEFAULT_DEADLINE_MS = 15e3;
|
|
4687
5281
|
var WC_SERIES_SLUG = "soccer-fifwc";
|
|
4688
5282
|
var WC_SPORT = "fifwc";
|
|
4689
5283
|
var KICKOFF_TOLERANCE_MS = 6 * 60 * 6e4;
|
|
@@ -4696,49 +5290,81 @@ var PolymarketProvider = class {
|
|
|
4696
5290
|
opts;
|
|
4697
5291
|
name = "polymarket";
|
|
4698
5292
|
async findSignal(match, options) {
|
|
4699
|
-
const deadline =
|
|
4700
|
-
return (await this.resolveOne(match, options, deadline))
|
|
5293
|
+
const deadline = Date.now() + (options?.deadlineMs ?? DEFAULT_DEADLINE_MS);
|
|
5294
|
+
return parsedValue(await this.resolveOne(match, options, deadline));
|
|
4701
5295
|
}
|
|
4702
5296
|
async findSignals(matches, options) {
|
|
4703
|
-
const
|
|
4704
|
-
const
|
|
4705
|
-
|
|
5297
|
+
const results = /* @__PURE__ */ new Map();
|
|
5298
|
+
const deadline = Date.now() + (options?.deadlineMs ?? DEFAULT_DEADLINE_MS);
|
|
5299
|
+
let complete = true;
|
|
4706
5300
|
for (const m of matches) {
|
|
4707
|
-
if (Date.now() >= deadline)
|
|
5301
|
+
if (Date.now() >= deadline) {
|
|
5302
|
+
results.set(m.id, unresolved("enrichment deadline expired"));
|
|
5303
|
+
complete = false;
|
|
5304
|
+
continue;
|
|
5305
|
+
}
|
|
4708
5306
|
const r = await this.resolveOne(m, options, deadline);
|
|
4709
|
-
if (r.
|
|
4710
|
-
|
|
5307
|
+
if (r.kind === "unresolved" || r.kind === "malformed") complete = false;
|
|
5308
|
+
results.set(m.id, r);
|
|
4711
5309
|
}
|
|
4712
|
-
return {
|
|
5310
|
+
return { results, complete };
|
|
4713
5311
|
}
|
|
4714
5312
|
/**
|
|
4715
|
-
* Resolve one match
|
|
4716
|
-
*
|
|
4717
|
-
*
|
|
4718
|
-
*
|
|
5313
|
+
* Resolve one match into a verdict.
|
|
5314
|
+
*
|
|
5315
|
+
* Every exit says which KIND of non-answer it is, because that decides
|
|
5316
|
+
* whether it may be remembered — see `isCacheable`: a conclusion we drew from
|
|
5317
|
+
* a payload we READ is cacheable (including an ambiguity, which is stable),
|
|
5318
|
+
* while a shape we could not read is not. Previously a single
|
|
5319
|
+
* `checked: boolean` collapsed five distinct situations into two, and the
|
|
5320
|
+
* ones that landed on the wrong side of it — an ambiguous payload, a
|
|
5321
|
+
* two-legged market, an incoherent 1X2 — were negative-cached as the fact
|
|
5322
|
+
* that this fixture has no market.
|
|
4719
5323
|
*/
|
|
4720
5324
|
async resolveOne(match, options, deadline = Number.POSITIVE_INFINITY) {
|
|
4721
|
-
const entry = (this.opts.mapping ?? BUNDLED_MAPPING)[match.id];
|
|
4722
|
-
const slugs = entry?.eventSlug ? [entry.eventSlug] : deriveEventSlugs(match);
|
|
4723
|
-
if (slugs.length === 0) return { checked: true };
|
|
4724
5325
|
const configured = options?.timeoutMs ?? this.opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
4725
5326
|
try {
|
|
5327
|
+
const entry = (this.opts.mapping ?? BUNDLED_MAPPING)[match.id];
|
|
5328
|
+
const slugs = entry?.eventSlug ? [entry.eventSlug] : deriveEventSlugs(match);
|
|
5329
|
+
if (slugs.length === 0) return definitiveNone("fixture has no derivable event slug");
|
|
5330
|
+
const RANK = {
|
|
5331
|
+
"definitive-none": 0,
|
|
5332
|
+
ambiguous: 1,
|
|
5333
|
+
unresolved: 2,
|
|
5334
|
+
malformed: 3
|
|
5335
|
+
};
|
|
5336
|
+
let worst;
|
|
5337
|
+
const keepWorst = (r) => {
|
|
5338
|
+
if (r.kind === "definitive-none" || r.kind === "valid") return;
|
|
5339
|
+
if (!worst || (RANK[r.kind] ?? 0) > (RANK[worst.kind] ?? 0)) worst = r;
|
|
5340
|
+
};
|
|
4726
5341
|
for (const slug of slugs) {
|
|
4727
5342
|
const remaining = deadline - Date.now();
|
|
4728
|
-
if (remaining <= 0) return
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
5343
|
+
if (remaining <= 0) return unresolved("deadline expired between candidate slugs");
|
|
5344
|
+
let found;
|
|
5345
|
+
try {
|
|
5346
|
+
found = await this.fetchEvent(slug, Math.min(configured, remaining));
|
|
5347
|
+
} catch {
|
|
5348
|
+
keepWorst(malformed("candidate request failed"));
|
|
5349
|
+
continue;
|
|
5350
|
+
}
|
|
5351
|
+
if (found.kind !== "valid") {
|
|
5352
|
+
keepWorst(found);
|
|
5353
|
+
continue;
|
|
5354
|
+
}
|
|
5355
|
+
const r = this.toSignal(match, slug, found.value, options);
|
|
5356
|
+
if (r.kind === "valid") return r;
|
|
5357
|
+
keepWorst(r);
|
|
4732
5358
|
}
|
|
4733
|
-
return
|
|
5359
|
+
return worst ?? definitiveNone("no candidate slug yielded a usable market");
|
|
4734
5360
|
} catch {
|
|
4735
|
-
return
|
|
5361
|
+
return malformed("provider request failed");
|
|
4736
5362
|
}
|
|
4737
5363
|
}
|
|
4738
5364
|
async fetchEvent(slug, timeoutMs) {
|
|
4739
5365
|
const base = this.opts.baseUrl ?? DEFAULT_BASE2;
|
|
4740
5366
|
assertAllowedHost(base);
|
|
4741
|
-
const url = `${base}/events
|
|
5367
|
+
const url = `${base}/events/slug/${encodeURIComponent(slug)}`;
|
|
4742
5368
|
const doFetch = this.opts.fetchImpl ?? fetch;
|
|
4743
5369
|
const res = await doFetch(url, {
|
|
4744
5370
|
signal: AbortSignal.timeout(timeoutMs ?? this.opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2),
|
|
@@ -4747,7 +5373,7 @@ var PolymarketProvider = class {
|
|
|
4747
5373
|
redirect: "error",
|
|
4748
5374
|
headers: { Accept: "application/json", "User-Agent": USER_AGENT2 }
|
|
4749
5375
|
});
|
|
4750
|
-
if (res.status === 404) return
|
|
5376
|
+
if (res.status === 404) return definitiveNone("slug returns 404");
|
|
4751
5377
|
if (!res.ok) {
|
|
4752
5378
|
throw new Error(`Polymarket request failed: ${res.status} ${res.statusText}`);
|
|
4753
5379
|
}
|
|
@@ -4756,63 +5382,148 @@ var PolymarketProvider = class {
|
|
|
4756
5382
|
throw new Error(`Polymarket response too large: ${length} bytes`);
|
|
4757
5383
|
}
|
|
4758
5384
|
const data = await res.json();
|
|
5385
|
+
if (Array.isArray(data) && data.length > 1) {
|
|
5386
|
+
return ambiguous("slug returned more than one event");
|
|
5387
|
+
}
|
|
5388
|
+
if (Array.isArray(data) && data.length === 0) return definitiveNone("slug returns no event");
|
|
4759
5389
|
const event = Array.isArray(data) ? data[0] : data;
|
|
4760
|
-
|
|
5390
|
+
if (!event || typeof event !== "object") return malformed("event body is not an object");
|
|
5391
|
+
return valid(event);
|
|
4761
5392
|
}
|
|
4762
5393
|
toSignal(match, eventSlug, event, options) {
|
|
4763
|
-
if (event.active
|
|
4764
|
-
|
|
4765
|
-
|
|
5394
|
+
if (typeof event.active !== "boolean" || typeof event.closed !== "boolean") {
|
|
5395
|
+
return malformed("event active/closed is not a boolean");
|
|
5396
|
+
}
|
|
5397
|
+
if (event.active === false || event.closed === true) {
|
|
5398
|
+
return definitiveNone("event is closed or inactive");
|
|
4766
5399
|
}
|
|
4767
|
-
if (event.
|
|
4768
|
-
|
|
5400
|
+
if (event.seriesSlug !== WC_SERIES_SLUG && event.sport?.sport !== WC_SPORT) {
|
|
5401
|
+
return definitiveNone("event is not in this competition");
|
|
5402
|
+
}
|
|
5403
|
+
if (typeof event.slug !== "string") {
|
|
5404
|
+
return malformed("event states no slug");
|
|
5405
|
+
}
|
|
5406
|
+
if (event.slug !== eventSlug) return definitiveNone("event is not the one requested");
|
|
5407
|
+
if (typeof event.startTime !== "string" || !canonicalTimestamp(event.startTime)) {
|
|
5408
|
+
return malformed("event startTime missing or unparseable");
|
|
5409
|
+
}
|
|
5410
|
+
const start = Date.parse(event.startTime);
|
|
4769
5411
|
const kick = Date.parse(match.kickoff);
|
|
4770
|
-
if (Number.isFinite(start)
|
|
4771
|
-
return
|
|
5412
|
+
if (!Number.isFinite(start) || !Number.isFinite(kick)) {
|
|
5413
|
+
return malformed("event or fixture kickoff is unreadable");
|
|
5414
|
+
}
|
|
5415
|
+
if (Math.abs(start - kick) > KICKOFF_TOLERANCE_MS) {
|
|
5416
|
+
return definitiveNone("event kickoff does not match the fixture");
|
|
4772
5417
|
}
|
|
4773
|
-
|
|
4774
|
-
(
|
|
5418
|
+
if (!Array.isArray(event.markets)) {
|
|
5419
|
+
return malformed("event markets is not an array");
|
|
5420
|
+
}
|
|
5421
|
+
const marketsTruncated = Array.isArray(event.markets) && event.markets.length > MAX_EVENT_MARKETS;
|
|
5422
|
+
if (marketsTruncated) {
|
|
5423
|
+
return malformed("event market list exceeded the cap");
|
|
5424
|
+
}
|
|
5425
|
+
const marketList = takeBounded(event.markets, MAX_EVENT_MARKETS);
|
|
5426
|
+
if (marketList.some(
|
|
5427
|
+
(market) => !market || typeof market !== "object" || typeof market.sportsMarketType !== "string"
|
|
5428
|
+
)) {
|
|
5429
|
+
return malformed("event market is missing its market-type discriminator");
|
|
5430
|
+
}
|
|
5431
|
+
const moneyline = marketList.filter(
|
|
5432
|
+
(m) => m?.sportsMarketType === "moneyline"
|
|
4775
5433
|
);
|
|
4776
|
-
const
|
|
4777
|
-
const
|
|
4778
|
-
const
|
|
4779
|
-
|
|
5434
|
+
const homeSel = pickMarket(moneyline, match.home.code, match.home.name);
|
|
5435
|
+
const awaySel = pickMarket(moneyline, match.away.code, match.away.name);
|
|
5436
|
+
const drawSel = pickDraw(moneyline);
|
|
5437
|
+
for (const [side, sel] of [
|
|
5438
|
+
["home", homeSel],
|
|
5439
|
+
["away", awaySel],
|
|
5440
|
+
["draw", drawSel]
|
|
5441
|
+
]) {
|
|
5442
|
+
if (sel.kind === "ambiguous") {
|
|
5443
|
+
return ambiguous(`${sel.count} markets claim the ${side} outcome`);
|
|
5444
|
+
}
|
|
5445
|
+
}
|
|
5446
|
+
if (homeSel.kind !== "one" || awaySel.kind !== "one" || drawSel.kind !== "one") {
|
|
5447
|
+
return definitiveNone("event does not carry all three 1X2 legs");
|
|
5448
|
+
}
|
|
5449
|
+
const homeMarket = homeSel.value;
|
|
5450
|
+
const awayMarket = awaySel.value;
|
|
5451
|
+
const drawMarket = drawSel.value;
|
|
4780
5452
|
const legIds = [homeMarket, awayMarket, drawMarket].filter((m) => m != null).map((m) => m.id ?? m.slug ?? "");
|
|
4781
|
-
if (new Set(legIds).size !== legIds.length)
|
|
5453
|
+
if (new Set(legIds).size !== legIds.length) {
|
|
5454
|
+
return ambiguous("two outcome legs are the same market");
|
|
5455
|
+
}
|
|
4782
5456
|
const legs = [
|
|
4783
5457
|
["home", homeMarket, match.home.code, match.home.name],
|
|
4784
5458
|
["draw", drawMarket, void 0, "Draw"],
|
|
4785
5459
|
["away", awayMarket, match.away.code, match.away.name]
|
|
4786
5460
|
];
|
|
4787
5461
|
const outcomes = [];
|
|
4788
|
-
let asOf = event.updatedAt;
|
|
5462
|
+
let asOf = canonicalTimestamp(event.updatedAt);
|
|
4789
5463
|
let liquidity;
|
|
4790
|
-
for (const [kind, market,
|
|
5464
|
+
for (const [kind, market, teamCode2, label] of legs) {
|
|
4791
5465
|
if (!market) continue;
|
|
4792
|
-
if (market.closed
|
|
4793
|
-
|
|
5466
|
+
if (typeof market.closed !== "boolean" || typeof market.active !== "boolean") {
|
|
5467
|
+
return malformed("market active/closed is not a boolean");
|
|
5468
|
+
}
|
|
5469
|
+
if (market.closed === true || market.active === false) {
|
|
5470
|
+
return definitiveNone("an outcome leg is closed or inactive");
|
|
5471
|
+
}
|
|
5472
|
+
if (market.description && NON_REGULAR_TIME.test(market.description)) {
|
|
5473
|
+
return definitiveNone("an outcome leg is not a regular-time market");
|
|
5474
|
+
}
|
|
4794
5475
|
const yes = yesPrice(market);
|
|
4795
|
-
if (yes == null) return
|
|
4796
|
-
outcomes.push({ kind, teamCode, label, probability: yes });
|
|
4797
|
-
|
|
4798
|
-
|
|
5476
|
+
if (yes == null) return malformed("market is not a readable Yes/No binary");
|
|
5477
|
+
outcomes.push({ kind, teamCode: teamCode2, label, probability: yes });
|
|
5478
|
+
const marketAsOf = canonicalTimestamp(market.updatedAt);
|
|
5479
|
+
if (!marketAsOf) {
|
|
5480
|
+
return malformed("market updatedAt missing or unparseable");
|
|
5481
|
+
}
|
|
5482
|
+
const nowMs = (options?.now ?? this.opts.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
5483
|
+
if (Date.parse(marketAsOf) - nowMs > FUTURE_SKEW_MS) {
|
|
5484
|
+
return malformed("market updatedAt is dated forward");
|
|
5485
|
+
}
|
|
5486
|
+
if (!asOf || Date.parse(marketAsOf) < Date.parse(asOf)) asOf = marketAsOf;
|
|
5487
|
+
const rawLiq = market.liquidityNum ?? market.liquidity;
|
|
5488
|
+
const liq = numberish(rawLiq);
|
|
5489
|
+
if (rawLiq != null && liq == null) {
|
|
5490
|
+
return malformed("market liquidity is unreadable");
|
|
5491
|
+
}
|
|
4799
5492
|
if (liq != null) liquidity = liquidity == null ? liq : Math.min(liquidity, liq);
|
|
4800
5493
|
}
|
|
4801
5494
|
const rawSum = outcomes.reduce((s, o) => s + o.probability, 0);
|
|
4802
|
-
if (rawSum < 0.9 || rawSum > 1.15)
|
|
5495
|
+
if (rawSum < 0.9 || rawSum > 1.15) {
|
|
5496
|
+
return ambiguous("outcome probabilities do not form a coherent 1X2");
|
|
5497
|
+
}
|
|
5498
|
+
if (!asOf) return malformed("no usable timestamp on the event or its markets");
|
|
4803
5499
|
const signal = buildMarketSignal({
|
|
4804
5500
|
match,
|
|
4805
5501
|
source: "polymarket",
|
|
4806
|
-
|
|
4807
|
-
|
|
5502
|
+
// Echoed into MCP structured content (tools.ts `market.id`), i.e. straight
|
|
5503
|
+
// into an agent's context. Stripping control characters is NOT sufficient
|
|
5504
|
+
// there: printable prose ("IGNORE PREVIOUS INSTRUCTIONS") survives that and
|
|
5505
|
+
// is precisely what matters for a model reading it. Gamma ids are short
|
|
5506
|
+
// opaque tokens, so validate that GRAMMAR and otherwise fall back to the
|
|
5507
|
+
// slug we derived ourselves.
|
|
5508
|
+
// The fallback is grammar-checked too. It is normally a slug we derived
|
|
5509
|
+
// ourselves, but `mapping.2026.json` can override it, so echoing it raw
|
|
5510
|
+
// was the one path around the agent-facing filter this line exists for.
|
|
5511
|
+
sourceMarketId: safeMarketId(event.id) ?? safeDerivedSlug(eventSlug),
|
|
5512
|
+
asOf,
|
|
4808
5513
|
outcomes,
|
|
4809
5514
|
liquidity,
|
|
4810
5515
|
now: options?.now ?? this.opts.now,
|
|
4811
5516
|
maxAgeMs: options?.maxAgeMs ?? this.opts.maxAgeMs
|
|
4812
5517
|
});
|
|
4813
|
-
return signal.ambiguous ?
|
|
5518
|
+
return signal.ambiguous ? ambiguous("signal does not map cleanly onto this fixture") : valid(signal);
|
|
4814
5519
|
}
|
|
4815
5520
|
};
|
|
5521
|
+
function safeMarketId(id) {
|
|
5522
|
+
return typeof id === "string" && /^[0-9]{1,32}$/.test(id) ? id : void 0;
|
|
5523
|
+
}
|
|
5524
|
+
function safeDerivedSlug(slug) {
|
|
5525
|
+
return typeof slug === "string" && /^fifwc-[a-z]{2,3}-[a-z]{2,3}-\d{4}-\d{2}-\d{2}$/.test(slug) ? slug : void 0;
|
|
5526
|
+
}
|
|
4816
5527
|
var POLYMARKET_TOKEN = {
|
|
4817
5528
|
SUI: "che",
|
|
4818
5529
|
// Switzerland
|
|
@@ -4826,8 +5537,16 @@ var POLYMARKET_TOKEN = {
|
|
|
4826
5537
|
// Croatia
|
|
4827
5538
|
COD: "cdr",
|
|
4828
5539
|
// DR Congo
|
|
4829
|
-
CPV: "cvi"
|
|
5540
|
+
CPV: "cvi",
|
|
4830
5541
|
// Cabo Verde
|
|
5542
|
+
// TWO letters, not three — the one entry that is not ISO alpha-3. Verified
|
|
5543
|
+
// live: `fifwc-kor-cze-2026-06-11` is a 404, `fifwc-kr-cze-2026-06-11`
|
|
5544
|
+
// resolves to "Korea Republic vs. Czechia". Korea's three group fixtures
|
|
5545
|
+
// therefore had no market line at all. The `^[a-z]{3}$` guard in
|
|
5546
|
+
// `deriveEventSlugs` validates the FIFA CODE, not the token, so a two-letter
|
|
5547
|
+
// alias passes through it unharmed.
|
|
5548
|
+
KOR: "kr"
|
|
5549
|
+
// Korea Republic
|
|
4831
5550
|
};
|
|
4832
5551
|
function pmTokens(code) {
|
|
4833
5552
|
const c = code.toLowerCase();
|
|
@@ -4858,18 +5577,21 @@ function slugToken(m) {
|
|
|
4858
5577
|
return (m.slug ?? "").toLowerCase().split("-").pop() ?? "";
|
|
4859
5578
|
}
|
|
4860
5579
|
function isDrawMarket(m) {
|
|
4861
|
-
|
|
5580
|
+
const title = (m.groupItemTitle ?? "").trim().toLowerCase();
|
|
5581
|
+
return slugToken(m) === "draw" || title === "draw" || /^draw\s*\(/.test(title);
|
|
4862
5582
|
}
|
|
4863
|
-
function pickMarket(markets,
|
|
4864
|
-
const tokens = pmTokens(
|
|
5583
|
+
function pickMarket(markets, teamCode2, teamName) {
|
|
5584
|
+
const tokens = pmTokens(teamCode2);
|
|
4865
5585
|
const name = teamName.trim().toLowerCase();
|
|
4866
5586
|
const teamMarkets = markets.filter((m) => !isDrawMarket(m));
|
|
4867
|
-
const bySlug = teamMarkets.
|
|
4868
|
-
if (bySlug) return bySlug;
|
|
4869
|
-
|
|
5587
|
+
const bySlug = teamMarkets.filter((m) => tokens.includes(slugToken(m)));
|
|
5588
|
+
if (bySlug.length > 1) return { kind: "ambiguous", count: bySlug.length };
|
|
5589
|
+
const byTitle = name ? teamMarkets.filter((m) => (m.groupItemTitle ?? "").trim().toLowerCase() === name) : [];
|
|
5590
|
+
if (byTitle.length > 1) return { kind: "ambiguous", count: byTitle.length };
|
|
5591
|
+
return selectOne([.../* @__PURE__ */ new Set([...bySlug, ...byTitle])]);
|
|
4870
5592
|
}
|
|
4871
5593
|
function pickDraw(markets) {
|
|
4872
|
-
return markets.
|
|
5594
|
+
return selectOne(markets.filter(isDrawMarket));
|
|
4873
5595
|
}
|
|
4874
5596
|
function assertAllowedHost(base) {
|
|
4875
5597
|
let host;
|
|
@@ -4883,20 +5605,29 @@ function assertAllowedHost(base) {
|
|
|
4883
5605
|
}
|
|
4884
5606
|
}
|
|
4885
5607
|
function yesPrice(market) {
|
|
4886
|
-
const labels = parseJsonArray(market.outcomes);
|
|
4887
|
-
const
|
|
4888
|
-
if (
|
|
4889
|
-
const
|
|
4890
|
-
if (
|
|
4891
|
-
const
|
|
4892
|
-
|
|
5608
|
+
const labels = parseJsonArray(market.outcomes).map((l) => l.trim().toLowerCase());
|
|
5609
|
+
const raw = parseJsonArray(market.outcomePrices);
|
|
5610
|
+
if (raw.some((v) => v.trim() === "" || !Number.isFinite(Number(v)))) return void 0;
|
|
5611
|
+
const prices = raw.map((v) => Number(v));
|
|
5612
|
+
if (labels.length !== 2 || prices.length !== 2) return void 0;
|
|
5613
|
+
const i = labels.indexOf("yes");
|
|
5614
|
+
const j = labels.indexOf("no");
|
|
5615
|
+
if (i < 0 || j < 0) return void 0;
|
|
5616
|
+
const yes = prices[i];
|
|
5617
|
+
const no = prices[j];
|
|
5618
|
+
if (![yes, no].every((v) => typeof v === "number" && Number.isFinite(v) && v >= 0 && v <= 1)) {
|
|
5619
|
+
return void 0;
|
|
5620
|
+
}
|
|
5621
|
+
if (Math.abs(yes + no - 1) > 0.05) return void 0;
|
|
5622
|
+
return yes > 0 ? yes : void 0;
|
|
4893
5623
|
}
|
|
4894
5624
|
function parseJsonArray(v) {
|
|
4895
|
-
|
|
5625
|
+
const asText = (x) => typeof x === "string" || typeof x === "number" ? String(x) : "";
|
|
5626
|
+
if (Array.isArray(v)) return v.map(asText);
|
|
4896
5627
|
if (typeof v === "string") {
|
|
4897
5628
|
try {
|
|
4898
5629
|
const parsed = JSON.parse(v);
|
|
4899
|
-
return Array.isArray(parsed) ? parsed.map(
|
|
5630
|
+
return Array.isArray(parsed) ? parsed.map(asText) : [];
|
|
4900
5631
|
} catch {
|
|
4901
5632
|
return [];
|
|
4902
5633
|
}
|
|
@@ -4904,10 +5635,10 @@ function parseJsonArray(v) {
|
|
|
4904
5635
|
return [];
|
|
4905
5636
|
}
|
|
4906
5637
|
function numberish(v) {
|
|
4907
|
-
if (typeof v === "number") return Number.isFinite(v) ? v : void 0;
|
|
5638
|
+
if (typeof v === "number") return Number.isFinite(v) && v >= 0 ? v : void 0;
|
|
4908
5639
|
if (typeof v === "string") {
|
|
4909
5640
|
const n = Number(v);
|
|
4910
|
-
return Number.isFinite(n) ? n : void 0;
|
|
5641
|
+
return Number.isFinite(n) && n >= 0 ? n : void 0;
|
|
4911
5642
|
}
|
|
4912
5643
|
return void 0;
|
|
4913
5644
|
}
|
|
@@ -4943,7 +5674,7 @@ async function getMarketSignals(provider, matches, options) {
|
|
|
4943
5674
|
try {
|
|
4944
5675
|
return await provider.findSignals(matches, options);
|
|
4945
5676
|
} catch {
|
|
4946
|
-
return
|
|
5677
|
+
return emptyBatch();
|
|
4947
5678
|
}
|
|
4948
5679
|
}
|
|
4949
5680
|
|
|
@@ -5026,6 +5757,9 @@ function formatShareSnippet(input, options = {}) {
|
|
|
5026
5757
|
if (input.degraded && input.matches.length > 0) {
|
|
5027
5758
|
blocks.push("(Live data unavailable \u2014 showing the bundled schedule, not live scores.)");
|
|
5028
5759
|
}
|
|
5760
|
+
if (includeMarkets && input.marketComplete === false) {
|
|
5761
|
+
blocks.push("(Market data unavailable or incomplete \u2014 not all fixtures were checked.)");
|
|
5762
|
+
}
|
|
5029
5763
|
blocks.push(
|
|
5030
5764
|
shareFooter({
|
|
5031
5765
|
source: input.source,
|
|
@@ -5056,7 +5790,9 @@ function formatShareTable(input, options = {}) {
|
|
|
5056
5790
|
const includeInstall = options.includeInstallLine !== false;
|
|
5057
5791
|
const blocks = [];
|
|
5058
5792
|
if (input.tables.length === 0) {
|
|
5059
|
-
blocks.push(
|
|
5793
|
+
blocks.push(
|
|
5794
|
+
input.emptyNote ?? (input.degraded ? "Live standings unavailable." : "No standings available.")
|
|
5795
|
+
);
|
|
5060
5796
|
} else {
|
|
5061
5797
|
for (const { group, rows } of input.tables) {
|
|
5062
5798
|
blocks.push(
|
|
@@ -5207,29 +5943,34 @@ export {
|
|
|
5207
5943
|
DEFAULT_FLAVOR,
|
|
5208
5944
|
DEFAULT_MAX_AGE_MS,
|
|
5209
5945
|
EspnAdapter,
|
|
5210
|
-
FEED_TEXT_MAX,
|
|
5211
5946
|
FLAVOR_LEVELS,
|
|
5212
5947
|
FakeMarketProvider,
|
|
5213
5948
|
KNOCKOUT_EXTRA_TIME_MS,
|
|
5214
5949
|
KNOWN_SOURCES,
|
|
5215
5950
|
LIVE_WINDOW_MS,
|
|
5951
|
+
MAX_LABEL_COLUMNS,
|
|
5216
5952
|
PolymarketProvider,
|
|
5217
5953
|
ProviderError,
|
|
5218
5954
|
SHARE_DISCLAIMER,
|
|
5219
5955
|
SHARE_HASHTAG,
|
|
5220
5956
|
allFixtures,
|
|
5221
5957
|
allTeams,
|
|
5958
|
+
ambiguous,
|
|
5222
5959
|
asFlavorLevel,
|
|
5960
|
+
bounded,
|
|
5223
5961
|
buildBracketTopology,
|
|
5224
5962
|
buildBracketView,
|
|
5225
5963
|
buildMarketSignal,
|
|
5226
5964
|
byKickoff,
|
|
5965
|
+
cacheableKeys,
|
|
5227
5966
|
competitionBase,
|
|
5228
5967
|
computeStandings,
|
|
5229
5968
|
countdown,
|
|
5230
5969
|
currentOrNextFixtureForTeam,
|
|
5970
|
+
definitiveNone,
|
|
5231
5971
|
deriveFavorite,
|
|
5232
5972
|
displayWidth,
|
|
5973
|
+
emptyBatch,
|
|
5233
5974
|
favoriteStrength,
|
|
5234
5975
|
fixturesByDate,
|
|
5235
5976
|
fixturesByGroup,
|
|
@@ -5257,12 +5998,15 @@ export {
|
|
|
5257
5998
|
getStandings,
|
|
5258
5999
|
groups,
|
|
5259
6000
|
hasSaneDistribution,
|
|
6001
|
+
humanLabel,
|
|
6002
|
+
isCacheable,
|
|
5260
6003
|
isFinished,
|
|
5261
6004
|
isFlavorLevel,
|
|
5262
6005
|
isLive,
|
|
5263
6006
|
isReliableMarketSignal,
|
|
5264
6007
|
isResolvedNation,
|
|
5265
6008
|
isStaleSignal,
|
|
6009
|
+
isTournamentWindowOver,
|
|
5266
6010
|
isValidDate,
|
|
5267
6011
|
isValidTimeZone,
|
|
5268
6012
|
knockoutWindow,
|
|
@@ -5273,6 +6017,7 @@ export {
|
|
|
5273
6017
|
lookupTeam,
|
|
5274
6018
|
makeAdapter,
|
|
5275
6019
|
makeMarketProvider,
|
|
6020
|
+
malformed,
|
|
5276
6021
|
mapEspnEvent,
|
|
5277
6022
|
mapsCleanly,
|
|
5278
6023
|
marketAttributionText,
|
|
@@ -5295,16 +6040,26 @@ export {
|
|
|
5295
6040
|
normalizeOutcomes,
|
|
5296
6041
|
outcomeFromScore,
|
|
5297
6042
|
padVisible,
|
|
6043
|
+
parseCachedMarketSignal,
|
|
6044
|
+
parseCachedMatch,
|
|
6045
|
+
parseCachedMatches,
|
|
5298
6046
|
parseStandings,
|
|
5299
6047
|
parseTeamSlot,
|
|
6048
|
+
parsedValue,
|
|
6049
|
+
productFlag,
|
|
5300
6050
|
resolveCompetition,
|
|
5301
6051
|
resolveMarketSource,
|
|
5302
6052
|
resolveTz,
|
|
6053
|
+
resolvedValues,
|
|
5303
6054
|
sanitizeBundledFixture,
|
|
5304
|
-
sanitizeFeedText,
|
|
5305
|
-
sanitizeMatchStrings,
|
|
5306
6055
|
scoreline,
|
|
6056
|
+
sealMarketSignal,
|
|
6057
|
+
sealMatch,
|
|
6058
|
+
selectOne,
|
|
5307
6059
|
stageLabel,
|
|
5308
6060
|
stageLabelI18n,
|
|
5309
|
-
t
|
|
6061
|
+
t,
|
|
6062
|
+
truncateVisible,
|
|
6063
|
+
unresolved,
|
|
6064
|
+
valid
|
|
5310
6065
|
};
|