feat: use Laya as a weighted signal in live stream grouping (#16)

Introduce a liveGroupingScoring engine that combines existing
membership signals (normalized name, brand, number, metadata tokens,
language, category/source when relevant) with a bounded Laya
contribution: Laya raises or lowers confidence instead of acting as
a final judge, with guard-rails against obvious contradictions.
Sanitize Laya batch identity inputs in layaService and wire the
weighted scoring into the live projection service. Weights are not
exposed in the Nodecast UI. Cover signal combination, bounds and
contradiction guard-rails with tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
blomios
2026-10-02 16:41:47 +02:00
parent a5ac3788a4
commit 925465ba39
5 changed files with 393 additions and 11 deletions

View File

@ -119,3 +119,24 @@ test('batch validation rejects oversized item counts', () => {
error => error instanceof LayaRequestError && error.statusCode === 413
);
});
test('identity hints send only sanitized channel metadata to Laya', async () => {
let requestBody;
const service = new LayaService({
cache: null,
fetch: async (url, options) => {
requestBody = JSON.parse(options.body);
return {
ok: true,
json: async () => ({ results: [{ answers: { sameIdentity: { noul: 0.9, confidence: 0.8 } } }] })
};
}
});
await service.identityHintBatch([{
id: 'pair',
left: { name: 'Sports http://user:pass@example.test/live', group: 'token=secret', source: 'Provider', tvgId: 'one', streamUrl: 'forbidden' },
right: { name: 'Sports', group: 'Group', source: 'Provider', tvgId: 'two', password: 'forbidden' }
}]);
assert.deepEqual(Object.keys(requestBody.states[0].left), ['name', 'group', 'source', 'tvgId']);
assert.doesNotMatch(JSON.stringify(requestBody), /example\.test|secret|forbidden|user:pass/);
});