From 0084177d157e7fbc1263f16d5d2a4d3e9850b078 Mon Sep 17 00:00:00 2001 From: nk Date: Sat, 6 Jun 2026 15:49:17 +0300 Subject: [PATCH] =?UTF-8?q?fix(charts):=20=D0=BE=D1=82=D1=81=D0=B5=D0=B2?= =?UTF-8?q?=20=D0=BC=D1=83=D1=81=D0=BE=D1=80=D0=B0=20=D0=B8=20=D1=80=D0=B0?= =?UTF-8?q?=D0=B7=D0=B3=D0=BE=D0=B2=D0=BE=D1=80=D0=BD=D1=8B=D1=85/=D1=88?= =?UTF-8?q?=D1=83=D1=82=D0=BE=D1=87=D0=BD=D1=8B=D1=85=20=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D0=BD=D1=86=D0=B8=D0=B9=20=D0=B8=D0=B7=20=D1=87=D0=B0=D1=80?= =?UTF-8?q?=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit recordPlay теперь не считает: разговорные/шуточные жанры (Кассиопея, Юмор ФМ, Рассказы, Радио Вера, Comedy Radio, ВГТРК, Старое радио) и мусорные названия (хекс-плейсхолдеры с цифрой, URL, числовые коды, artist==song). Исторический мусор почищен напрямую в БД (−4273 трека, −13274 плея талк/комеди-станций). Co-Authored-By: Claude Opus 4.8 --- src/charts/charts.service.ts | 46 ++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/charts/charts.service.ts b/src/charts/charts.service.ts index 53b4b80..8e5af83 100644 --- a/src/charts/charts.service.ts +++ b/src/charts/charts.service.ts @@ -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(); + private excludedStationIdsAt = 0; + + private async getExcludedStationIds(): Promise> { + 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 { 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()) ||