@claudinho/core 0.9.1 → 0.9.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
@@ -385,9 +385,48 @@ declare function nextFixtureForTeam(code: string, opts?: {
385
385
  from?: Date;
386
386
  fixtures?: Match[];
387
387
  }): Match | undefined;
388
- /** A fixture is potentially live from kickoff until ~kickoff + 140 min. */
388
+ /**
389
+ * A group/regular fixture is potentially live from kickoff until ~kickoff + 140
390
+ * min (90' + half-time + stoppage).
391
+ */
389
392
  declare const LIVE_WINDOW_MS: number;
390
- /** Fixtures whose live window contains `now` (cheap, static — no network). */
393
+ /**
394
+ * Extra allowance for knockout matches, which can go to extra time (+30') and a
395
+ * penalty shootout, running to ~kickoff + 190 min — well past LIVE_WINDOW_MS.
396
+ * Without it the statusline refresher stops polling at kickoff+140min and the
397
+ * cache goes stale mid-match, so a knockout tie still level after 90' (in ET or
398
+ * penalties) silently drops off the statusline while it's still being played.
399
+ * Numerically matches the slack `marketFixtureForTeam` already applies (live.ts).
400
+ */
401
+ declare const KNOCKOUT_EXTRA_TIME_MS: number;
402
+ /** Live-window length for a fixture — longer for knockout (extra time + penalties). */
403
+ declare function liveWindowMsFor(m: Match): number;
404
+ /**
405
+ * True once EVERY bundled fixture's (stage-aware) live window has ELAPSED —
406
+ * derived from the bundled schedule rather than a hardcoded date (same reasoning
407
+ * as the derived knockout window: the `CLAUDINHO_COMPETITION` seam must not carry
408
+ * a dead calendar).
409
+ *
410
+ * Named for exactly what it measures. This is a TIME predicate, not proof that
411
+ * every match was played: the bundled schedule is a **resultless skeleton** (every
412
+ * fixture ships `SCHEDULED` by design), so status can't inform it — a POSTPONED or
413
+ * abandoned fixture still reads as elapsed once its window passes. That is
414
+ * acceptable for the only use it has (turning a spent schedule into a sign-off),
415
+ * and it's why this is not called `isTournamentComplete`. **Make it status-aware
416
+ * against the live overlay before reusing it for anything that asserts results.**
417
+ *
418
+ * Deliberately a GLOBAL condition, independent of any team filter: an eliminated
419
+ * team mid-tournament has no next fixture either, and must NOT read as "over".
420
+ * Every other empty case still fails closed to the neutral `⚽ —`.
421
+ *
422
+ * Callers must additionally confirm the bundled schedule even applies — it
423
+ * describes the DEFAULT competition, so `CLAUDINHO_COMPETITION` pointing elsewhere
424
+ * makes this answer meaningless (see the `DEFAULT_COMPETITION` gates in the CLI).
425
+ *
426
+ * Empty fixture list → false: "we know nothing" is not "it's over".
427
+ */
428
+ declare function isTournamentWindowOver(now?: number, fixtures?: Match[]): boolean;
429
+ /** Fixtures whose (stage-aware) live window contains `now` (cheap, static — no network). */
391
430
  declare function fixturesInLiveWindow(now?: number, fixtures?: Match[]): Match[];
392
431
  /**
393
432
  * The team's in-play fixture when one is inside its live window, else the next
@@ -1279,4 +1318,4 @@ declare function formatShareBracket(input: ShareBracketInput, options?: ShareBra
1279
1318
  /** Compact one-line-per-match bracket for narrow share contexts. */
1280
1319
  declare function formatBracketCompactLine(mv: BracketMatchView, opts?: BracketFormatOpts): string;
1281
1320
 
1282
- export { BRACKET_STAGE_ORDER, type BracketFormatOpts, type BracketMatchNode, type BracketMatchView, type BracketResult, type BracketTopology, type BracketView, type BuildSignalInput, DEFAULT_COMPETITION, DEFAULT_FLAVOR, DEFAULT_MAX_AGE_MS, EspnAdapter, type EspnAdapterOptions, FEED_TEXT_MAX, FLAVOR_LEVELS, FakeMarketProvider, type FakeMarketProviderOptions, type FavoriteStrength, type FlavorLevel, type FormatOpts, type GroupStandings, KNOWN_SOURCES, type KnockoutFixturesResult, LIVE_WINDOW_MS, type Lang, 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 NextFixtureResult, type Outcome, PolymarketProvider, type PolymarketProviderOptions, type ProviderAdapter, type ProviderCapabilities, ProviderError, type ProviderErrorKind, type PunditPick, type ResolvedParticipant, SHARE_DISCLAIMER, SHARE_HASHTAG, type ShareBracketInput, type ShareBracketOptions, type ShareSnippetInput, type ShareSnippetOptions, type ShareStyle, type ShareTableInput, type SlotRef, type Stage, type StandingRow, type StandingsResult, type Status, type Team, type TeamInfo, type TeamLookup, allFixtures, allTeams, asFlavorLevel, buildBracketTopology, buildBracketView, buildMarketSignal, byKickoff, competitionBase, computeStandings, countdown, currentOrNextFixtureForTeam, deriveFavorite, displayWidth, favoriteStrength, fixturesByDate, fixturesByGroup, fixturesByTeam, fixturesInLiveWindow, flagEmoji, formatBracketCompactLine, formatBracketList, formatBracketMatchLine, formatBracketTree, formatDate, formatKickoff, formatShareBracket, formatShareSnippet, formatShareTable, formatTime, getBracket, getKnockoutFixtures, getLiveMatches, getMarketSignal, getMarketSignals, getMatchById, getMatchesForDate, getNextFixtureForTeam, getStandings, groups, hasSaneDistribution, isFinished, isFlavorLevel, isLive, isReliableMarketSignal, isResolvedNation, isStaleSignal, isValidDate, isValidTimeZone, knockoutWindow, liveSourceLabel, loadBracketTopology, localDate, lookupTeam, makeAdapter, makeMarketProvider, mapEspnEvent, mapsCleanly, marketAttributionText, marketBlock, marketFavoriteText, marketFixtureForTeam, marketLine, marketProbabilityText, marketRelevant, marketSignalRendersFor, marketSourceLabel, matchFlavor, matchKey, matchLocation, mergeLive, nationToFlag, nationToRegion, nextFixtureForTeam, normalizeLang, normalizeOutcomes, outcomeFromScore, padVisible, parseStandings, parseTeamSlot, resolveCompetition, resolveMarketSource, resolveTz, sanitizeBundledFixture, sanitizeFeedText, sanitizeMatchStrings, scoreline, stageLabel, stageLabelI18n, t };
1321
+ export { BRACKET_STAGE_ORDER, type BracketFormatOpts, type BracketMatchNode, type BracketMatchView, type BracketResult, type BracketTopology, type BracketView, type BuildSignalInput, DEFAULT_COMPETITION, DEFAULT_FLAVOR, DEFAULT_MAX_AGE_MS, EspnAdapter, type EspnAdapterOptions, FEED_TEXT_MAX, FLAVOR_LEVELS, FakeMarketProvider, type FakeMarketProviderOptions, type FavoriteStrength, type FlavorLevel, type FormatOpts, type GroupStandings, KNOCKOUT_EXTRA_TIME_MS, KNOWN_SOURCES, type KnockoutFixturesResult, LIVE_WINDOW_MS, type Lang, 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 NextFixtureResult, type Outcome, PolymarketProvider, type PolymarketProviderOptions, type ProviderAdapter, type ProviderCapabilities, ProviderError, type ProviderErrorKind, type PunditPick, type ResolvedParticipant, SHARE_DISCLAIMER, SHARE_HASHTAG, type ShareBracketInput, type ShareBracketOptions, type ShareSnippetInput, type ShareSnippetOptions, type ShareStyle, type ShareTableInput, type SlotRef, type Stage, type StandingRow, type StandingsResult, type Status, type Team, type TeamInfo, type TeamLookup, allFixtures, allTeams, asFlavorLevel, buildBracketTopology, buildBracketView, buildMarketSignal, byKickoff, competitionBase, computeStandings, countdown, currentOrNextFixtureForTeam, deriveFavorite, displayWidth, favoriteStrength, fixturesByDate, fixturesByGroup, fixturesByTeam, fixturesInLiveWindow, flagEmoji, formatBracketCompactLine, formatBracketList, formatBracketMatchLine, formatBracketTree, formatDate, formatKickoff, formatShareBracket, formatShareSnippet, formatShareTable, formatTime, getBracket, getKnockoutFixtures, getLiveMatches, getMarketSignal, getMarketSignals, getMatchById, getMatchesForDate, getNextFixtureForTeam, getStandings, groups, hasSaneDistribution, isFinished, isFlavorLevel, isLive, isReliableMarketSignal, isResolvedNation, isStaleSignal, isTournamentWindowOver, isValidDate, isValidTimeZone, knockoutWindow, liveSourceLabel, liveWindowMsFor, loadBracketTopology, localDate, lookupTeam, makeAdapter, makeMarketProvider, mapEspnEvent, mapsCleanly, marketAttributionText, marketBlock, marketFavoriteText, marketFixtureForTeam, marketLine, marketProbabilityText, marketRelevant, marketSignalRendersFor, marketSourceLabel, matchFlavor, matchKey, matchLocation, mergeLive, nationToFlag, nationToRegion, nextFixtureForTeam, normalizeLang, normalizeOutcomes, outcomeFromScore, padVisible, parseStandings, parseTeamSlot, resolveCompetition, resolveMarketSource, resolveTz, sanitizeBundledFixture, sanitizeFeedText, sanitizeMatchStrings, scoreline, stageLabel, stageLabelI18n, t };
package/dist/index.js CHANGED
@@ -2941,10 +2941,22 @@ function nextFixtureForTeam(code, opts = {}) {
2941
2941
  );
2942
2942
  }
2943
2943
  var LIVE_WINDOW_MS = 140 * 6e4;
2944
+ var KNOCKOUT_EXTRA_TIME_MS = 60 * 6e4;
2945
+ function liveWindowMsFor(m) {
2946
+ return m.stage === "GROUP" || m.stage === "FRIENDLY" ? LIVE_WINDOW_MS : LIVE_WINDOW_MS + KNOCKOUT_EXTRA_TIME_MS;
2947
+ }
2948
+ function isTournamentWindowOver(now = Date.now(), fixtures = SCHEDULE) {
2949
+ if (fixtures.length === 0) return false;
2950
+ return fixtures.every((m) => {
2951
+ const k = Date.parse(m.kickoff);
2952
+ if (Number.isNaN(k)) return false;
2953
+ return now > k + liveWindowMsFor(m);
2954
+ });
2955
+ }
2944
2956
  function fixturesInLiveWindow(now = Date.now(), fixtures = SCHEDULE) {
2945
2957
  return fixtures.filter((m) => {
2946
2958
  const k = Date.parse(m.kickoff);
2947
- return now >= k && now <= k + LIVE_WINDOW_MS;
2959
+ return now >= k && now <= k + liveWindowMsFor(m);
2948
2960
  }).sort(byKickoff);
2949
2961
  }
2950
2962
  function currentOrNextFixtureForTeam(code, opts = {}) {
@@ -2952,7 +2964,7 @@ function currentOrNextFixtureForTeam(code, opts = {}) {
2952
2964
  const nowMs = from.getTime();
2953
2965
  const inPlay = fixturesByTeam(code, opts.fixtures ?? SCHEDULE).find((m) => {
2954
2966
  const k = Date.parse(m.kickoff);
2955
- return nowMs >= k && nowMs <= k + LIVE_WINDOW_MS;
2967
+ return nowMs >= k && nowMs <= k + liveWindowMsFor(m);
2956
2968
  });
2957
2969
  return inPlay ?? nextFixtureForTeam(code, opts);
2958
2970
  }
@@ -3086,7 +3098,7 @@ function rosterAtZero(matches) {
3086
3098
  var ESPN_SOCCER = "https://site.api.espn.com/apis/site/v2/sports/soccer";
3087
3099
  var DEFAULT_COMPETITION = "fifa.world";
3088
3100
  var DEFAULT_BASE = `${ESPN_SOCCER}/${DEFAULT_COMPETITION}`;
3089
- var USER_AGENT = `claudinho/${"0.9.1"} (+https://github.com/arturogarrido/claudinho)`;
3101
+ var USER_AGENT = `claudinho/${"0.9.3"} (+https://github.com/arturogarrido/claudinho)`;
3090
3102
  var MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
3091
3103
  function competitionBase(slug) {
3092
3104
  return `${ESPN_SOCCER}/${slug}`;
@@ -5206,6 +5218,7 @@ export {
5206
5218
  FEED_TEXT_MAX,
5207
5219
  FLAVOR_LEVELS,
5208
5220
  FakeMarketProvider,
5221
+ KNOCKOUT_EXTRA_TIME_MS,
5209
5222
  KNOWN_SOURCES,
5210
5223
  LIVE_WINDOW_MS,
5211
5224
  PolymarketProvider,
@@ -5258,10 +5271,12 @@ export {
5258
5271
  isReliableMarketSignal,
5259
5272
  isResolvedNation,
5260
5273
  isStaleSignal,
5274
+ isTournamentWindowOver,
5261
5275
  isValidDate,
5262
5276
  isValidTimeZone,
5263
5277
  knockoutWindow,
5264
5278
  liveSourceLabel,
5279
+ liveWindowMsFor,
5265
5280
  loadBracketTopology,
5266
5281
  localDate,
5267
5282
  lookupTeam,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claudinho/core",
3
- "version": "0.9.1",
3
+ "version": "0.9.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",
@@ -53,12 +53,12 @@
53
53
  "agent"
54
54
  ],
55
55
  "devDependencies": {
56
- "@types/node": "^22.20.0",
57
- "@vitest/coverage-v8": "^4.1.9",
56
+ "@types/node": "^22.20.1",
57
+ "@vitest/coverage-v8": "^4.1.10",
58
58
  "tsup": "^8.0.0",
59
- "tsx": "^4.23.0",
59
+ "tsx": "^4.23.1",
60
60
  "typescript": "^5.7.0",
61
- "vitest": "^4.1.9"
61
+ "vitest": "^4.1.10"
62
62
  },
63
63
  "scripts": {
64
64
  "build": "tsup",