@claudinho/core 0.4.2 → 0.4.3

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/dist/index.d.ts CHANGED
@@ -224,6 +224,21 @@ declare function nextFixtureForTeam(code: string, opts?: {
224
224
  from?: Date;
225
225
  fixtures?: Match[];
226
226
  }): Match | undefined;
227
+ /** A fixture is potentially live from kickoff until ~kickoff + 140 min. */
228
+ declare const LIVE_WINDOW_MS: number;
229
+ /** Fixtures whose live window contains `now` (cheap, static — no network). */
230
+ declare function fixturesInLiveWindow(now?: number, fixtures?: Match[]): Match[];
231
+ /**
232
+ * The team's in-play fixture when one is inside its live window, else the next
233
+ * upcoming fixture. This is "the match that matters for <team> right now":
234
+ * mid-match, a user (or an agent) asking about a team almost always means the
235
+ * one being played — `nextFixtureForTeam` alone would skip it the moment the
236
+ * kickoff is in the past and silently answer about next week.
237
+ */
238
+ declare function currentOrNextFixtureForTeam(code: string, opts?: {
239
+ from?: Date;
240
+ fixtures?: Match[];
241
+ }): Match | undefined;
227
242
  /** Sorted list of distinct group letters present in the schedule. */
228
243
  declare function groups(fixtures?: Match[]): string[];
229
244
 
@@ -412,6 +427,34 @@ declare function liveSourceLabel(source: string): string;
412
427
  * schedule on any provider/network error (graceful degradation).
413
428
  */
414
429
  declare function getMatchesForDate(adapter: ProviderAdapter, dateISO: string): Promise<LiveResult>;
430
+ interface MatchByIdResult {
431
+ match?: Match;
432
+ degraded: boolean;
433
+ source?: string;
434
+ }
435
+ /**
436
+ * The fixture a team-scoped MARKET query should be about, live-confirmed.
437
+ * Static window math alone fails twice at the edges: a match in extra time
438
+ * (now > kickoff + 140min, still LIVE) would be skipped for next week's
439
+ * fixture, and a just-finished match (inside the window, already FT) would be
440
+ * selected over the next one. So: pick the in-window candidate using a widened
441
+ * window, overlay live state, and fall through to the next fixture when the
442
+ * overlay says the candidate is finished. Degraded fetches keep the static
443
+ * candidate (fail-closed: the market-relevance gate then errs toward showing
444
+ * nothing rather than something wrong).
445
+ */
446
+ declare function marketFixtureForTeam(adapter: ProviderAdapter, code: string, now?: Date): Promise<MatchByIdResult>;
447
+ /**
448
+ * A single match by id, with live overlay. The provider's scoreboard buckets
449
+ * days in its own zone (ESPN: US/Eastern), so a fixture's UTC date can differ
450
+ * from the scoreboard day it's filed under — a 02:00Z kickoff belongs to the
451
+ * previous ET evening, and fetching only the UTC date silently misses its live
452
+ * state (the match then renders from the static schedule as if scheduled).
453
+ * Fetch the ±1-day window around the fixture's UTC date instead — the same
454
+ * trick `getMatchesForDate` uses — and fall back to the static fixture on any
455
+ * provider error.
456
+ */
457
+ declare function getMatchById(adapter: ProviderAdapter, id: string): Promise<MatchByIdResult>;
415
458
  /** Currently-live matches; empty + degraded on error. */
416
459
  declare function getLiveMatches(adapter: ProviderAdapter): Promise<LiveResult>;
417
460
 
@@ -520,14 +563,16 @@ interface MarketProvider {
520
563
  findSignals(matches: Match[], options?: MarketSignalOptions): Promise<MarketSignalsResult>;
521
564
  }
522
565
 
523
- /**
524
- * Probability normalization + the reliability gate. `isReliableMarketSignal` is
525
- * the single primitive every default-on surface keys off: "show only when
526
- * reliable, otherwise omit silently."
527
- */
528
-
529
566
  /** Default freshness window: a signal older than this is stale (15 minutes). */
530
567
  declare const DEFAULT_MAX_AGE_MS: number;
568
+ /**
569
+ * Market signals are pre-match and in-play reads. Once a match has finished —
570
+ * status `FT`, or (for static fixtures that never get a live overlay) once its
571
+ * live window has long passed — a "favorite" is resolved history: showing
572
+ * "markets favor X 100%" after full time reads as a bug, not information.
573
+ * An unparseable kickoff fails open (the reliability gate still applies).
574
+ */
575
+ declare function marketRelevant(match: Match, now?: Date): boolean;
531
576
  /**
532
577
  * Re-scale outcome probabilities so the positive ones sum to 1. This removes
533
578
  * market "vig" (raw prices sum to >1) and is scale-agnostic: inputs may be
@@ -763,4 +808,4 @@ interface ShareSnippetInput {
763
808
  */
764
809
  declare function formatShareSnippet(input: ShareSnippetInput, options?: ShareSnippetOptions): string;
765
810
 
766
- export { type BuildSignalInput, DEFAULT_COMPETITION, DEFAULT_FLAVOR, DEFAULT_MAX_AGE_MS, EspnAdapter, type EspnAdapterOptions, FLAVOR_LEVELS, FakeMarketProvider, type FakeMarketProviderOptions, type FavoriteStrength, type FlavorLevel, type FormatOpts, type LedgerRow, type LiveResult, type MapContext, type MarketFavorite, type MarketMapping, type MarketMappingTable, type MarketOutcome, type MarketOutcomeKind, type MarketProvider, type MarketSignal, type MarketSignalOptions, type MarketSignalsResult, type Match, type MatchEvent, type Outcome, PolymarketProvider, type PolymarketProviderOptions, type ProviderAdapter, type ProviderCapabilities, type PunditPick, SHARE_DISCLAIMER, SHARE_HASHTAG, type ShareSnippetInput, type ShareSnippetOptions, type ShareStyle, type Stage, type StandingRow, type Status, type Team, allFixtures, asFlavorLevel, buildMarketSignal, byKickoff, competitionBase, computeStandings, countdown, deriveFavorite, favoriteStrength, fixturesByDate, fixturesByGroup, fixturesByTeam, flagEmoji, formatDate, formatKickoff, formatShareSnippet, formatTime, getLiveMatches, getMarketSignal, getMarketSignals, getMatchesForDate, groups, hasSaneDistribution, isFinished, isFlavorLevel, isLive, isReliableMarketSignal, isStaleSignal, isValidDate, isValidTimeZone, liveSourceLabel, localDate, makeAdapter, makeMarketProvider, mapEspnEvent, mapsCleanly, marketAttributionText, marketBlock, marketFavoriteText, marketLine, marketProbabilityText, marketSourceLabel, matchFlavor, matchLocation, mergeLive, nationToFlag, nationToRegion, nextFixtureForTeam, normalizeOutcomes, outcomeFromScore, resolveCompetition, resolveMarketSource, resolveTz, scoreline };
811
+ export { type BuildSignalInput, DEFAULT_COMPETITION, DEFAULT_FLAVOR, DEFAULT_MAX_AGE_MS, EspnAdapter, type EspnAdapterOptions, FLAVOR_LEVELS, FakeMarketProvider, type FakeMarketProviderOptions, type FavoriteStrength, type FlavorLevel, type FormatOpts, LIVE_WINDOW_MS, type LedgerRow, type LiveResult, type MapContext, type MarketFavorite, type MarketMapping, type MarketMappingTable, type MarketOutcome, type MarketOutcomeKind, type MarketProvider, type MarketSignal, type MarketSignalOptions, type MarketSignalsResult, type Match, type MatchByIdResult, type MatchEvent, type Outcome, PolymarketProvider, type PolymarketProviderOptions, type ProviderAdapter, type ProviderCapabilities, type PunditPick, SHARE_DISCLAIMER, SHARE_HASHTAG, type ShareSnippetInput, type ShareSnippetOptions, type ShareStyle, type Stage, type StandingRow, type Status, type Team, allFixtures, asFlavorLevel, buildMarketSignal, byKickoff, competitionBase, computeStandings, countdown, currentOrNextFixtureForTeam, deriveFavorite, favoriteStrength, fixturesByDate, fixturesByGroup, fixturesByTeam, fixturesInLiveWindow, flagEmoji, formatDate, formatKickoff, formatShareSnippet, formatTime, getLiveMatches, getMarketSignal, getMarketSignals, getMatchById, getMatchesForDate, groups, hasSaneDistribution, isFinished, isFlavorLevel, isLive, isReliableMarketSignal, isStaleSignal, isValidDate, isValidTimeZone, liveSourceLabel, localDate, makeAdapter, makeMarketProvider, mapEspnEvent, mapsCleanly, marketAttributionText, marketBlock, marketFavoriteText, marketFixtureForTeam, marketLine, marketProbabilityText, marketRelevant, marketSourceLabel, matchFlavor, matchLocation, mergeLive, nationToFlag, nationToRegion, nextFixtureForTeam, normalizeOutcomes, outcomeFromScore, resolveCompetition, resolveMarketSource, resolveTz, scoreline };
package/dist/index.js CHANGED
@@ -2622,6 +2622,22 @@ function nextFixtureForTeam(code, opts = {}) {
2622
2622
  (m) => new Date(m.kickoff).getTime() >= from.getTime()
2623
2623
  );
2624
2624
  }
2625
+ var LIVE_WINDOW_MS = 140 * 6e4;
2626
+ function fixturesInLiveWindow(now = Date.now(), fixtures = SCHEDULE) {
2627
+ return fixtures.filter((m) => {
2628
+ const k = Date.parse(m.kickoff);
2629
+ return now >= k && now <= k + LIVE_WINDOW_MS;
2630
+ }).sort(byKickoff);
2631
+ }
2632
+ function currentOrNextFixtureForTeam(code, opts = {}) {
2633
+ const from = opts.from ?? /* @__PURE__ */ new Date();
2634
+ const nowMs = from.getTime();
2635
+ const inPlay = fixturesByTeam(code, opts.fixtures ?? SCHEDULE).find((m) => {
2636
+ const k = Date.parse(m.kickoff);
2637
+ return nowMs >= k && nowMs <= k + LIVE_WINDOW_MS;
2638
+ });
2639
+ return inPlay ?? nextFixtureForTeam(code, opts);
2640
+ }
2625
2641
  function groups(fixtures = SCHEDULE) {
2626
2642
  const set = /* @__PURE__ */ new Set();
2627
2643
  for (const m of fixtures) if (m.group) set.add(m.group);
@@ -2885,6 +2901,33 @@ function shiftUtcDate(dateISO, days) {
2885
2901
  const [y, m, d] = dateISO.slice(0, 10).split("-").map(Number);
2886
2902
  return new Date(Date.UTC(y ?? 1970, (m ?? 1) - 1, (d ?? 1) + days)).toISOString().slice(0, 10);
2887
2903
  }
2904
+ var EXTRA_TIME_SLACK_MS = 60 * 6e4;
2905
+ async function marketFixtureForTeam(adapter, code, now = /* @__PURE__ */ new Date()) {
2906
+ const nowMs = now.getTime();
2907
+ const candidate = fixturesByTeam(code).find((m) => {
2908
+ const k = Date.parse(m.kickoff);
2909
+ return nowMs >= k && nowMs <= k + LIVE_WINDOW_MS + EXTRA_TIME_SLACK_MS;
2910
+ });
2911
+ if (candidate) {
2912
+ const r = await getMatchById(adapter, candidate.id);
2913
+ const m = r.match ?? candidate;
2914
+ if (!isFinished(m.status)) return { ...r, match: m };
2915
+ }
2916
+ const next = nextFixtureForTeam(code, { from: now });
2917
+ return { match: next, degraded: false };
2918
+ }
2919
+ async function getMatchById(adapter, id) {
2920
+ const base = allFixtures().find((m) => m.id === id);
2921
+ if (!base) return { match: void 0, degraded: false };
2922
+ const day = base.kickoff.slice(0, 10);
2923
+ try {
2924
+ const live = adapter.fetchWindow ? await adapter.fetchWindow(shiftUtcDate(day, -1), shiftUtcDate(day, 1)) : await adapter.fetchByDate(day);
2925
+ const hit = live.find((m) => m.id === id);
2926
+ return { match: hit ?? base, degraded: false, source: hit ? adapter.name : void 0 };
2927
+ } catch {
2928
+ return { match: base, degraded: true };
2929
+ }
2930
+ }
2888
2931
  async function getLiveMatches(adapter) {
2889
2932
  try {
2890
2933
  return { matches: await adapter.fetchLive(), degraded: false, source: adapter.name };
@@ -2895,6 +2938,12 @@ async function getLiveMatches(adapter) {
2895
2938
 
2896
2939
  // src/markets/normalize.ts
2897
2940
  var DEFAULT_MAX_AGE_MS = 15 * 6e4;
2941
+ function marketRelevant(match, now = /* @__PURE__ */ new Date()) {
2942
+ if (isLive(match.status)) return true;
2943
+ if (isFinished(match.status)) return false;
2944
+ const k = Date.parse(match.kickoff);
2945
+ return !Number.isFinite(k) || now.getTime() <= k + LIVE_WINDOW_MS;
2946
+ }
2898
2947
  function normalizeOutcomes(outcomes) {
2899
2948
  const sum = outcomes.reduce(
2900
2949
  (s, o) => s + (Number.isFinite(o.probability) && o.probability > 0 ? o.probability : 0),
@@ -3415,6 +3464,7 @@ export {
3415
3464
  EspnAdapter,
3416
3465
  FLAVOR_LEVELS,
3417
3466
  FakeMarketProvider,
3467
+ LIVE_WINDOW_MS,
3418
3468
  PolymarketProvider,
3419
3469
  SHARE_DISCLAIMER,
3420
3470
  SHARE_HASHTAG,
@@ -3425,11 +3475,13 @@ export {
3425
3475
  competitionBase,
3426
3476
  computeStandings,
3427
3477
  countdown,
3478
+ currentOrNextFixtureForTeam,
3428
3479
  deriveFavorite,
3429
3480
  favoriteStrength,
3430
3481
  fixturesByDate,
3431
3482
  fixturesByGroup,
3432
3483
  fixturesByTeam,
3484
+ fixturesInLiveWindow,
3433
3485
  flagEmoji,
3434
3486
  formatDate,
3435
3487
  formatKickoff,
@@ -3438,6 +3490,7 @@ export {
3438
3490
  getLiveMatches,
3439
3491
  getMarketSignal,
3440
3492
  getMarketSignals,
3493
+ getMatchById,
3441
3494
  getMatchesForDate,
3442
3495
  groups,
3443
3496
  hasSaneDistribution,
@@ -3457,8 +3510,10 @@ export {
3457
3510
  marketAttributionText,
3458
3511
  marketBlock,
3459
3512
  marketFavoriteText,
3513
+ marketFixtureForTeam,
3460
3514
  marketLine,
3461
3515
  marketProbabilityText,
3516
+ marketRelevant,
3462
3517
  marketSourceLabel,
3463
3518
  matchFlavor,
3464
3519
  matchLocation,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claudinho/core",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Domain model, provider adapters (ESPN), standings, bundled schedule, and the read-only Polymarket market-signal sidecar powering Claudinho. Not affiliated with FIFA or Anthropic.",
5
5
  "type": "module",
6
6
  "license": "MIT",