From ee5faeff77b73ff0e57a3653c9f103a42d6a1a67 Mon Sep 17 00:00:00 2001 From: Vladislav Kopnin Date: Sat, 10 Oct 2026 13:21:45 +0300 Subject: [PATCH] =?UTF-8?q?=D0=90=D1=83=D0=B4=D0=B8=D1=82=D0=BE=D1=80?= =?UTF-8?q?=D0=B8=D0=B8:=20=D0=B8=D0=B7=20=D0=BE=D0=BF=D0=B8=D1=81=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE=D0=BA=D0=B0=D0=B7=D1=8B=D0=B2?= =?UTF-8?q?=D0=B0=D0=B5=D0=BC=20=D1=82=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20?= =?UTF-8?q?=D1=82=D0=BE,=20=D1=87=D0=B5=D0=B3=D0=BE=20=D0=BD=D0=B5=D1=82?= =?UTF-8?q?=20=D0=B2=20=D0=B0=D1=82=D1=80=D0=B8=D0=B1=D1=83=D1=82=D0=B0?= =?UTF-8?q?=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Раньше описание выводилось целиком, и у аудиторий вида 405 («пк») получался дубль: «ПК» в атрибутах и «пк» строкой ниже. Теперь описание разбирается по частям: вместимость вырезается, а куски, которые целиком состоят из типа/оснащения/дней/номера, не показываются. Остаются только реальные дополнения: «без проектора», «МО и СТ», «амфитиатром». Слова не режутся по частям (раньше появлялись осколки вида «а», «-», «мелова»); короткие теги («ПК») учитываются, регистр сохраняется. --- src/frontend/static/app.js | 48 ++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/src/frontend/static/app.js b/src/frontend/static/app.js index a7f7038..c15622d 100644 --- a/src/frontend/static/app.js +++ b/src/frontend/static/app.js @@ -449,13 +449,53 @@ function daysPhrase(days) { return `по ${forms.slice(0, -1).join(", ")} и ${forms[forms.length - 1]}`; } -/** Описание из таблицы, если оно не сводится к номеру и дням (иначе не показываем). */ +/** + * Дополнение из таблицы: оставляем только фразы, которых нет в разобранных + * атрибутах («без проектора», «МО и СТ»), не разрезая слова. Если описание + * целиком сводится к типу/оснащению/вместимости/номеру/дням — не показываем. + */ function roomInfoText(room) { const info = (room.info || "").trim(); if (!info) return ""; - let rest = info.replace(/\d+/g, ""); - (room.available_days || []).forEach((day) => { rest = rest.split(day).join(""); }); - return rest.replace(/[\s;,.]+/g, "") ? info : ""; + + const known = new Set(); + const addText = (text) => + (text || "") + .toLowerCase() + .split(/[\s;,.:()\/-]+/) + .forEach((word) => { if (word.length >= 2) known.add(word); }); + addText(room.type); + addText(room.type_group); + (room.equipment || []).forEach(addText); + (room.available_days || []).forEach(addText); + + const STOP = new Set(["и", "в", "на", "для", "по", "с", "из", "до", "за", "от", "к", "о", "об", "при", "под", "над", "у"]); + const CAPACITY = /\d+\s*(?:мест[а-яё]*|раб\.?|осн\.?)/gi; + const isKnown = (token) => { + const clean = token.toLowerCase().replace(/[^а-яёa-z0-9]+/g, ""); + if (!clean || !/[а-яёa-z]/.test(clean)) return true; // числа и знаки + if (clean.length < 2) return true; + if (STOP.has(clean)) return true; + if (/^(мест|раб|осн)/.test(clean)) return true; // «место», «раб.» + return [...known].some( + (k) => clean.startsWith(k.slice(0, 4)) || k.startsWith(clean.slice(0, 4)), + ); + }; + + return info + .replace(CAPACITY, " ") + .split(/[;,]/) + .map((part) => part.trim()) + .filter(Boolean) + .map((part) => { + const tokens = part.split(/\s+/); + if (tokens.every(isKnown)) return ""; + const rest = [...tokens]; + while (rest.length && isKnown(rest[0])) rest.shift(); // убираем ведущие известные слова + return rest.join(" "); + }) + .filter(Boolean) + .join("; "); } /** Русское склонение существительного при числе. */