feat: Diversitäts-Filter gegen Sprachregion-Überflutung, höhere vote_count-Schwelle
This commit is contained in:
@@ -531,12 +531,22 @@ router.get('/streaming/:providerId', authenticate, async (req, res) => {
|
||||
const minYear = new Date().getFullYear() - 1;
|
||||
const minDate = `${minYear}-01-01`;
|
||||
|
||||
// Höhere vote_count-Schwelle filtert Nischentitel jeder Herkunft heraus
|
||||
// (nicht nach Sprache diskriminierend – ein Nischenfilm aus den USA fliegt genauso raus
|
||||
// wie ein Nischenfilm aus Korea; nur wirklich bekannte Titel bleiben drin)
|
||||
const MIN_VOTES = 300;
|
||||
// Zusätzliche Diversitäts-Deckelung: max. 3 Titel je Originalsprache in den Top 10,
|
||||
// damit eine einzelne Produktionsregion (z.B. durch viele ähnliche K-Dramen) die Liste
|
||||
// nicht komplett dominiert – ECHTE Hits (z.B. Squid Game) rutschen wegen hoher vote_count
|
||||
// ohnehin weit nach oben und werden davon nicht ausgebremst.
|
||||
const MAX_PER_LANGUAGE = 3;
|
||||
|
||||
const movieParams = {
|
||||
watch_region: 'DE',
|
||||
with_watch_providers: provider.tmdbId,
|
||||
with_watch_monetization_types: 'flatrate',
|
||||
sort_by: 'popularity.desc',
|
||||
'vote_count.gte': 10,
|
||||
'vote_count.gte': MIN_VOTES,
|
||||
'primary_release_date.gte': minDate,
|
||||
};
|
||||
const tvParams = {
|
||||
@@ -544,24 +554,57 @@ router.get('/streaming/:providerId', authenticate, async (req, res) => {
|
||||
with_watch_providers: provider.tmdbId,
|
||||
with_watch_monetization_types: 'flatrate',
|
||||
sort_by: 'popularity.desc',
|
||||
'vote_count.gte': 10,
|
||||
'vote_count.gte': MIN_VOTES,
|
||||
'first_air_date.gte': minDate,
|
||||
};
|
||||
|
||||
const [movieResults, tvResults, genreMap] = await Promise.all([
|
||||
tmdb('/discover/movie', movieParams),
|
||||
tmdb('/discover/tv', tvParams),
|
||||
// 3 Seiten (≈60 Titel) holen, damit nach der Diversitäts-Deckelung noch genug übrig bleibt
|
||||
const [movieP1, movieP2, movieP3, tvP1, tvP2, tvP3, genreMap] = await Promise.all([
|
||||
tmdb('/discover/movie', { ...movieParams, page: 1 }),
|
||||
tmdb('/discover/movie', { ...movieParams, page: 2 }),
|
||||
tmdb('/discover/movie', { ...movieParams, page: 3 }),
|
||||
tmdb('/discover/tv', { ...tvParams, page: 1 }),
|
||||
tmdb('/discover/tv', { ...tvParams, page: 2 }),
|
||||
tmdb('/discover/tv', { ...tvParams, page: 3 }),
|
||||
getGenreMap(),
|
||||
]);
|
||||
|
||||
const movies = (movieResults.results || [])
|
||||
.filter(m => !BLOCKED_LANGS.has(m.original_language))
|
||||
.slice(0, 10)
|
||||
// Diversitäts-Deckelung anwenden: Liste bleibt nach Popularität sortiert,
|
||||
// aber sobald eine Sprache MAX_PER_LANGUAGE erreicht hat, werden weitere
|
||||
// Titel dieser Sprache übersprungen (sie können trotzdem auftauchen sobald
|
||||
// ein Slot durch erschöpfte andere Sprachen frei bleibt – siehe Fallback unten).
|
||||
function applyDiversityCap(rawList) {
|
||||
const langCount = {};
|
||||
const picked = [];
|
||||
const overflow = [];
|
||||
for (const item of rawList) {
|
||||
const lang = item.original_language || 'xx';
|
||||
if ((langCount[lang] || 0) < MAX_PER_LANGUAGE) {
|
||||
picked.push(item);
|
||||
langCount[lang] = (langCount[lang] || 0) + 1;
|
||||
} else {
|
||||
overflow.push(item); // falls 10 nicht erreicht werden, später auffüllen
|
||||
}
|
||||
if (picked.length >= 10) break;
|
||||
}
|
||||
// Falls durch die Deckelung weniger als 10 zusammenkommen, mit den
|
||||
// nächstbeliebtesten Titeln auffüllen (auch wenn Deckel überschritten wird)
|
||||
for (const item of overflow) {
|
||||
if (picked.length >= 10) break;
|
||||
picked.push(item);
|
||||
}
|
||||
return picked.slice(0, 10);
|
||||
}
|
||||
|
||||
const movieRaw = [...(movieP1.results||[]), ...(movieP2.results||[]), ...(movieP3.results||[])]
|
||||
.filter(m => !BLOCKED_LANGS.has(m.original_language));
|
||||
const tvRaw = [...(tvP1.results||[]), ...(tvP2.results||[]), ...(tvP3.results||[])]
|
||||
.filter(m => !BLOCKED_LANGS.has(m.original_language));
|
||||
|
||||
const movies = applyDiversityCap(movieRaw)
|
||||
.map((m, i) => ({ ...enrichMovie(m, genreMap, null, i + 1), media_type: 'movie' }));
|
||||
|
||||
const tvShows = (tvResults.results || [])
|
||||
.filter(m => !BLOCKED_LANGS.has(m.original_language))
|
||||
.slice(0, 10)
|
||||
const tvShows = applyDiversityCap(tvRaw)
|
||||
.map((m, i) => ({
|
||||
id: m.id, media_type: 'tv', rank: i + 1,
|
||||
title: m.name || m.original_name,
|
||||
@@ -574,7 +617,15 @@ router.get('/streaming/:providerId', authenticate, async (req, res) => {
|
||||
origin_country: m.origin_country || [],
|
||||
}));
|
||||
|
||||
res.json({ provider: { id: provider.id, label: provider.label }, movies, tvShows });
|
||||
res.json({
|
||||
provider: { id: provider.id, label: provider.label },
|
||||
movies, tvShows,
|
||||
filterInfo: {
|
||||
minYear,
|
||||
minVotes: MIN_VOTES,
|
||||
maxPerLanguage: MAX_PER_LANGUAGE,
|
||||
},
|
||||
});
|
||||
} catch(e) { res.status(500).json({ error: e.message }); }
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user