fix(charts): отсев мусора и разговорных/шуточных станций из чарта
recordPlay теперь не считает: разговорные/шуточные жанры (Кассиопея, Юмор ФМ, Рассказы, Радио Вера, Comedy Radio, ВГТРК, Старое радио) и мусорные названия (хекс-плейсхолдеры с цифрой, URL, числовые коды, artist==song). Исторический мусор почищен напрямую в БД (−4273 трека, −13274 плея талк/комеди-станций). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,18 @@ import { Injectable, Logger, NotFoundException } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { EnrichmentService } from '../enrich/enrichment.service';
|
||||
|
||||
// Жанры, исключённые из чарта: разговорные/шуточные/без названий треков.
|
||||
// Их «треки» — это названия передач/реприз/спектаклей, не музыка.
|
||||
const EXCLUDED_CHART_GENRES = [
|
||||
'Станция Кассиопея',
|
||||
'Юмор ФМ',
|
||||
'Рассказы',
|
||||
'Радио Вера',
|
||||
'Comedy Radio',
|
||||
'ВГТРК',
|
||||
'Старое радио',
|
||||
];
|
||||
|
||||
// Период чарта
|
||||
export type ChartPeriod = 'day' | 'week' | 'month' | 'all';
|
||||
|
||||
@@ -109,6 +121,35 @@ export class ChartsService {
|
||||
return this.stationNames;
|
||||
}
|
||||
|
||||
// Кэш id станций исключённых жанров (разговорные/шуточные — не в чарт)
|
||||
private excludedStationIds = new Set<string>();
|
||||
private excludedStationIdsAt = 0;
|
||||
|
||||
private async getExcludedStationIds(): Promise<Set<string>> {
|
||||
const now = Date.now();
|
||||
if (now - this.excludedStationIdsAt > 10 * 60 * 1000 || this.excludedStationIds.size === 0) {
|
||||
const rows = await this.prisma.station.findMany({
|
||||
where: { genre: { in: EXCLUDED_CHART_GENRES } },
|
||||
select: { id: true },
|
||||
});
|
||||
this.excludedStationIds = new Set(rows.map((r) => r.id));
|
||||
this.excludedStationIdsAt = now;
|
||||
}
|
||||
return this.excludedStationIds;
|
||||
}
|
||||
|
||||
// Мусорный «трек»: хекс-плейсхолдер, URL, числовой код, или artist == song
|
||||
// (целый тайтл без нормального разбиения на исполнителя/название).
|
||||
private isJunkTrack(artist: string, song: string): boolean {
|
||||
const a = artist.trim();
|
||||
const s = song.trim();
|
||||
if (a.toLowerCase() === s.toLowerCase()) return true;
|
||||
const hex = (v: string) => /^[0-9a-f]{6,}$/i.test(v) && /[0-9]/.test(v);
|
||||
const code = (v: string) => /^[0-9]+-[0-9]+/.test(v);
|
||||
const url = (v: string) => /\.(ru|fm|by|com|ua)$/i.test(v) || /^https?:/i.test(v);
|
||||
return [a, s].some((v) => hex(v) || code(v) || url(v));
|
||||
}
|
||||
|
||||
// Записывает факт смены трека на станции (вызывается из NowPlayingService)
|
||||
async recordPlay(params: RecordPlayParams): Promise<void> {
|
||||
try {
|
||||
@@ -119,6 +160,11 @@ export class ChartsService {
|
||||
// Отсекаем не-музыкальные записи: пустые поля, либо когда артист/песня
|
||||
// совпадает с названием станции (это джинглы, шоу, сетевые промо).
|
||||
if (!artist || !song) return;
|
||||
// Разговорные/шуточные станции — не в чарт.
|
||||
const excluded = await this.getExcludedStationIds();
|
||||
if (excluded.has(stationDbId)) return;
|
||||
// Мусорные названия (хекс/URL/код/artist==song) — не в чарт.
|
||||
if (this.isJunkTrack(artist, song)) return;
|
||||
const stationNames = await this.getStationNames();
|
||||
if (
|
||||
stationNames.has(artist.toLowerCase()) ||
|
||||
|
||||
Reference in New Issue
Block a user