fix: group beIN Sports 1 variants and clarify Laya language warning (#15)

beIN Sports 1 variants stayed separated after projection because
names with region prefixes (C Car, C Reu, Thai), time-shift suffixes
(2h/4h/8h/12h) and language markers (En, Fr, Thai) produced distinct
fingerprints. Extend normalization in the server projection service
and the frontend grouping fallback to fold these into one logical
channel, with tests on the exact reported names. Clarify the Laya
language detection warning so a language-matching failure is not
mistaken for a grouping failure, and bump script versions for cache
refresh.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
blomios
2026-10-02 16:22:57 +02:00
parent dcec7648da
commit dbd5b7fea3
7 changed files with 106 additions and 6 deletions

View File

@ -21,6 +21,38 @@ test('does not merge distinct numbered channels', () => {
assert.deepEqual(logical.map(item => item.fingerprint), ['bein sports 1', 'bein sports 2']);
});
test('normalizes captured beIN Sports aliases like the server projection', () => {
const aliases = [
'C Car beIN Sports 1',
'C Reu beIN Sports 1',
'Thai beIN Sports 1 Thai',
'beIN Sports 1',
'beIN Sports 1 12h',
'beIN Sports 1 2h',
'beIN Sports 1 4h',
'beIN Sports 1 8h',
'beIN Sports 1 Appel',
'beIN Sports 1 En',
'beIN Sports 1 Fr',
'beIN Sports 1 Tod'
];
assert.deepEqual(aliases.map(grouping.normalizeChannelName), Array(aliases.length).fill('bein sports 1'));
const logical = grouping.groupChannels(aliases.map((name, index) => channel(String(index), name)));
assert.equal(logical.length, 1);
assert.equal(logical[0].variants.length, aliases.length);
});
test('keeps distinct beIN numbers and unknown suffixes separate', () => {
assert.equal(grouping.normalizeChannelName('beIN Sports 1'), 'bein sports 1');
assert.equal(grouping.normalizeChannelName('beIN Sports 2'), 'bein sports 2');
assert.equal(grouping.normalizeChannelName('beIN Sports Max 10'), 'bein sports max 10');
assert.equal(grouping.normalizeChannelName('Partner beIN Sports 1 Event'), 'bein sports 1 event');
assert.notEqual(
grouping.normalizeChannelName('Partner beIN Sports 1 Event'),
grouping.normalizeChannelName('beIN Sports 1')
);
});
test('preserves precomputed projection boundaries and display names', () => {
const logical = grouping.groupChannels([
channel('1', 'Shared Name HD', { logicalId: 'logical:a', logicalFingerprint: 'a', logicalDisplayName: 'Backend A' }),

View File

@ -55,7 +55,10 @@ test('ChannelList warning only appears for active Laya modes when unavailable or
channelListWarning('hybrid', { modelPresent: true, runtimeAvailable: false }),
'Local AI service is unavailable. Standard channel matching is being used.'
);
assert.equal(channelListWarning('laya', ready, true), 'Local AI language matching failed. Standard channel matching is being used.');
assert.equal(
channelListWarning('laya', ready, true),
'Local AI language detection failed. Channels were grouped with Standard matching and remain available.'
);
});
test('installed model with unavailable service is not reported ready', () => {

View File

@ -1,6 +1,7 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const {
normalizeChannelName,
projectCatalog,
serializeChannels,
startRebuild
@ -63,12 +64,40 @@ test('projects beIN quality variants into one logical channel', () => {
assert.deepEqual(groups[0].variants.map(variant => variant.variantRank), [0, 1, 2]);
});
test('normalizes the captured beIN Sports 1 aliases to one exact fingerprint', () => {
const aliases = [
'C Car beIN Sports 1',
'C Reu beIN Sports 1',
'Thai beIN Sports 1 Thai',
'beIN Sports 1',
'beIN Sports 1 12h',
'beIN Sports 1 2h',
'beIN Sports 1 4h',
'beIN Sports 1 8h',
'beIN Sports 1 Appel',
'beIN Sports 1 En',
'beIN Sports 1 Fr',
'beIN Sports 1 Tod'
];
assert.deepEqual(aliases.map(normalizeChannelName), Array(aliases.length).fill('bein sports 1'));
const groups = projectCatalog(aliases.map((name, index) => item(index, name)));
assert.equal(groups.length, 1);
assert.equal(groups[0].fingerprint, 'bein sports 1');
assert.equal(groups[0].variants.length, aliases.length);
});
test('does not merge distinct numbered beIN channels', () => {
const groups = projectCatalog([
item('1', 'beIN Sports 1 HD'),
item('2', 'beIN Sports 2 HD')
]);
assert.deepEqual(groups.map(group => group.fingerprint), ['bein sports 1', 'bein sports 2']);
assert.equal(normalizeChannelName('beIN Sports Max 10'), 'bein sports max 10');
});
test('keeps unknown beIN suffixes distinct instead of applying fuzzy matching', () => {
assert.equal(normalizeChannelName('Partner beIN Sports 1 Event'), 'bein sports 1 event');
assert.notEqual(normalizeChannelName('Partner beIN Sports 1 Event'), normalizeChannelName('beIN Sports 1'));
});
test('renaming a stream changes its projection membership and input hash', () => {