From 101108cd4452d2fcbe5bfdef0563bde870acdcf5 Mon Sep 17 00:00:00 2001 From: Nico Oelgart Date: Fri, 29 May 2026 09:47:39 +0200 Subject: [PATCH 1/2] Fix Link matcher --- src/Domain/YouTube/YouTubeMatchScorer.php | 15 ++- .../Domain/YouTube/YouTubeLinkMatcherTest.php | 27 ++++++ .../Domain/YouTube/YouTubeMatchScorerTest.php | 33 +++++++ .../CalendarSpeedRelayFormattingTest.php | 93 +++++++++++++++++++ 4 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 tests/unit/Infrastructure/Calendar/CalendarSpeedRelayFormattingTest.php diff --git a/src/Domain/YouTube/YouTubeMatchScorer.php b/src/Domain/YouTube/YouTubeMatchScorer.php index 5865b06..f9b2d6c 100644 --- a/src/Domain/YouTube/YouTubeMatchScorer.php +++ b/src/Domain/YouTube/YouTubeMatchScorer.php @@ -88,11 +88,22 @@ private function videoTitleContainsSameLocationAndSeason(string $videoTitle, IFS } } - return + if ( str_contains( str_replace(' ', '', $normalizedTitle), str_replace(' ', '', $this->textNormalizer->normalize($event->location)), - ); + ) + ) { + return true; + } + + foreach ($this->eventNameTokens($this->textNormalizer->normalize($event->eventName)) as $token) { + if (str_contains($normalizedTitle, $token)) { + return true; + } + } + + return false; } /** @param Tag[] $videoTags */ diff --git a/tests/unit/Domain/YouTube/YouTubeLinkMatcherTest.php b/tests/unit/Domain/YouTube/YouTubeLinkMatcherTest.php index cd5f447..6a09e11 100644 --- a/tests/unit/Domain/YouTube/YouTubeLinkMatcherTest.php +++ b/tests/unit/Domain/YouTube/YouTubeLinkMatcherTest.php @@ -272,6 +272,33 @@ final class YouTubeLinkMatcherTest extends TestCase $this->assertSame('https://youtu.be/para-qual-id', $liveStream->url); } + #[Test] public function alcobendas_location_matches_comunidad_de_madrid_video_title(): void + { + $event = $this->createEvent( + eventName: 'World Climbing Series Comunidad de Madrid 2026', + location: 'Alcobendas', + localStartDate: '2026-05-28T08:00:00Z', + localEndDate: '2026-05-30T20:00:00Z', + ); + $videoCollection = new YouTubeVideoCollection(); + $videoCollection->add(new YouTubeVideo( + title: "Men's Boulder semi-final | Comunidad de Madrid 2026", + duration: 120, + videoId: 'sfA6nto-LFw', + publishedAt: new DateTimeImmutable('2026-05-28T22:16:13Z'), + scheduledStartTime: null, + restrictedRegions: [], + )); + + $liveStream = $this->linkMatcher->findStreamUrlForRound( + event: $event, + roundName: "Men's Boulder Semi-Final", + videoCollection: $videoCollection, + ); + + $this->assertSame('https://youtu.be/sfA6nto-LFw', $liveStream->url); + } + private function createVideoCollection(): YouTubeVideoCollection { $titles = [ diff --git a/tests/unit/Domain/YouTube/YouTubeMatchScorerTest.php b/tests/unit/Domain/YouTube/YouTubeMatchScorerTest.php index 6f15625..06202c0 100644 --- a/tests/unit/Domain/YouTube/YouTubeMatchScorerTest.php +++ b/tests/unit/Domain/YouTube/YouTubeMatchScorerTest.php @@ -227,6 +227,39 @@ final class YouTubeMatchScorerTest extends TestCase $this->assertNotNull($score); } + #[Test] public function event_name_tokens_match_when_location_differs(): void + { + $video = $this->createVideo( + title: "Women's Boulder semi-final | Comunidad de Madrid 2026", + duration: 120, + publishedAt: '2026-05-29T10:00:00Z', + scheduledStartTime: '2026-05-29T12:00:00Z', + ); + + $score = $this->matchScorer->score( + video: $video, + roundTags: $this->roundTags("Women's Boulder Semi-Final"), + event: new IFSCEventInfo( + eventId: 1479, + eventName: 'World Climbing Series Comunidad de Madrid 2026', + slug: 'world-climbing-series-comunidad-de-madrid-2026', + leagueId: 10, + leagueName: 'World Climbing Series', + leagueSeasonId: 99, + localStartDate: '2026-05-28T08:00:00Z', + localEndDate: '2026-05-30T20:00:00Z', + timeZone: new DateTimeZone('Europe/Madrid'), + location: 'Alcobendas', + country: 'ESP', + disciplines: [], + categories: [], + ), + ); + + $this->assertNotNull($score); + $this->assertGreaterThanOrEqual(14, $score); + } + /** @return Tag[] */ private function roundTags(string $roundName): array { diff --git a/tests/unit/Infrastructure/Calendar/CalendarSpeedRelayFormattingTest.php b/tests/unit/Infrastructure/Calendar/CalendarSpeedRelayFormattingTest.php new file mode 100644 index 0000000..980a8db --- /dev/null +++ b/tests/unit/Infrastructure/Calendar/CalendarSpeedRelayFormattingTest.php @@ -0,0 +1,93 @@ + + */ +namespace SportClimbing\IfscCalendar\tests\Infrastructure\Calendar; + +use DateTimeImmutable; +use DateTimeZone; +use PHPUnit\Framework\Attributes\Test; +use PHPUnit\Framework\TestCase; +use SportClimbing\IfscCalendar\Domain\Discipline\IFSCDiscipline; +use SportClimbing\IfscCalendar\Domain\Discipline\IFSCDisciplines; +use SportClimbing\IfscCalendar\Domain\Event\IFSCEvent; +use SportClimbing\IfscCalendar\Domain\Round\IFSCRound; +use SportClimbing\IfscCalendar\Domain\Round\IFSCRoundCategory; +use SportClimbing\IfscCalendar\Domain\Round\IFSCRoundKind; +use SportClimbing\IfscCalendar\Domain\Round\IFSCRoundStatus; +use SportClimbing\IfscCalendar\Domain\Season\IFSCSeasonYear; +use SportClimbing\IfscCalendar\Domain\Stream\LiveStream; +use SportClimbing\IfscCalendar\Infrastructure\Calendar\CalendarFactory; +use SportClimbing\IfscCalendar\Infrastructure\Calendar\ICalCalendar; +use SportClimbing\IfscCalendar\Infrastructure\Calendar\JsonCalendar; + +final class CalendarSpeedRelayFormattingTest extends TestCase +{ + #[Test] public function json_calendar_uses_speed_relay_in_round_name_and_speed_in_disciplines(): void + { + $calendar = new JsonCalendar(); + $output = $calendar->generateForEvents([$this->createEventWithSpeedRelayRound()]); + $payload = json_decode($output, associative: true, flags: JSON_THROW_ON_ERROR); + + $this->assertSame(['speed'], $payload['events'][0]['disciplines']); + $this->assertSame("Men's Speed Relay Qualification", $payload['events'][0]['rounds'][0]['name']); + $this->assertSame(['speed'], $payload['events'][0]['rounds'][0]['disciplines']); + } + + #[Test] public function ical_calendar_uses_speed_relay_in_summary(): void + { + $calendar = new ICalCalendar( + calendarFactory: new CalendarFactory(), + productIdentifier: '-//ifsc-calendar//tests//EN', + publishedTtl: 'PT1H', + calendarName: 'IFSC Calendar Tests', + ); + + $output = $calendar->generateForEvents([$this->createEventWithSpeedRelayRound()]); + + $this->assertStringContainsString("Men's Speed Relay Qualification - Madrid (ES)", $output); + $this->assertStringNotContainsString('Speed_Relay', $output); + } + + private function createEventWithSpeedRelayRound(): IFSCEvent + { + $timeZone = new DateTimeZone('Europe/Madrid'); + $roundStart = new DateTimeImmutable('2026-09-01 10:00:00', $timeZone); + $roundEnd = new DateTimeImmutable('2026-09-01 11:30:00', $timeZone); + $round = new IFSCRound( + name: "Men's Speed Relay Qualification", + categories: [IFSCRoundCategory::MEN], + disciplines: new IFSCDisciplines([IFSCDiscipline::SPEED_RELAY]), + kind: IFSCRoundKind::QUALIFICATION, + liveStream: new LiveStream(url: 'https://youtube.com/watch?v=relay-test'), + startTime: $roundStart, + endTime: $roundEnd, + status: IFSCRoundStatus::CONFIRMED, + ); + + return new IFSCEvent( + season: IFSCSeasonYear::SEASON_2026, + eventId: 9999, + slug: 'ifsc-speed-relay-test-2026', + leagueName: 'World Cups and World Championships', + timeZone: $timeZone, + eventName: 'IFSC Speed Relay Test Event 2026', + location: 'Madrid', + country: 'ES', + siteUrl: 'https://ifsc.stream/season/2026/event/ifsc-speed-relay-test-2026', + infosheetUrl: null, + startsAt: $roundStart, + endsAt: $roundEnd, + disciplines: [IFSCDiscipline::SPEED_RELAY], + rounds: [$round], + startList: [], + startListTotal: 0, + ticketsSummary: null, + ticketsPurchaseUrl: null, + countryName: 'Spain', + ); + } +} From e8fbab67cf749a99336222fced6760c01bc61265 Mon Sep 17 00:00:00 2001 From: Nico Oelgart Date: Fri, 29 May 2026 09:53:38 +0200 Subject: [PATCH 2/2] Fix Link matcher --- src/Domain/YouTube/YouTubeMatchScorer.php | 4 +++ .../Domain/YouTube/YouTubeMatchScorerTest.php | 32 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/Domain/YouTube/YouTubeMatchScorer.php b/src/Domain/YouTube/YouTubeMatchScorer.php index f9b2d6c..09b4eb6 100644 --- a/src/Domain/YouTube/YouTubeMatchScorer.php +++ b/src/Domain/YouTube/YouTubeMatchScorer.php @@ -98,6 +98,10 @@ private function videoTitleContainsSameLocationAndSeason(string $videoTitle, IFS } foreach ($this->eventNameTokens($this->textNormalizer->normalize($event->eventName)) as $token) { + if (is_numeric($token)) { + continue; + } + if (str_contains($normalizedTitle, $token)) { return true; } diff --git a/tests/unit/Domain/YouTube/YouTubeMatchScorerTest.php b/tests/unit/Domain/YouTube/YouTubeMatchScorerTest.php index 06202c0..578265b 100644 --- a/tests/unit/Domain/YouTube/YouTubeMatchScorerTest.php +++ b/tests/unit/Domain/YouTube/YouTubeMatchScorerTest.php @@ -260,6 +260,38 @@ final class YouTubeMatchScorerTest extends TestCase $this->assertGreaterThanOrEqual(14, $score); } + #[Test] public function different_location_video_is_rejected_even_when_same_year(): void + { + $video = $this->createVideo( + title: "Lead semi-finals | Wujiang 2026", + duration: 90, + publishedAt: '2026-05-21T11:00:00Z', + scheduledStartTime: '2026-05-21T12:00:00Z', + ); + + $score = $this->matchScorer->score( + video: $video, + roundTags: $this->roundTags("Men's & Women's Lead Semi-Final"), + event: new IFSCEventInfo( + eventId: 1500, + eventName: 'World Climbing Series Chamonix 2026', + slug: 'world-climbing-series-chamonix-2026', + leagueId: 10, + leagueName: 'World Climbing Series', + leagueSeasonId: 99, + localStartDate: '2026-05-20T08:00:00Z', + localEndDate: '2026-05-22T20:00:00Z', + timeZone: new DateTimeZone('Europe/Paris'), + location: 'Chamonix', + country: 'FRA', + disciplines: [], + categories: [], + ), + ); + + $this->assertNull($score); + } + /** @return Tag[] */ private function roundTags(string $roundName): array {