Вынести условия-предикаты в extension-методы

HasUser из 13 endpoint-файлов сведён в AuthHelpers.HasUser;
15 приватных предикатов заменены extension-методами с удалением
дублирующих приватных методов: IsCommunicationFailure,
IsTransportFailure, IsPrivateEndpoint, IsConfigured, IsTrue,
IsExpired, IsFailedLogin/IsSuccessfulLogin, HasChanges, HasBudget,
HasAnyTerm, IsCurrencyLetter, IsEmojiCodePoint, ContainsFooterHint,
IsTypeLabel.
This commit is contained in:
Rustam Khalimov
2026-09-11 13:08:47 +03:00
parent 9e07568ddd
commit 413eaac48c
40 changed files with 1091 additions and 993 deletions
@@ -0,0 +1,18 @@
namespace Deal.Modules.Pipeline.Application.Parse;
/// <summary>
/// Расширения кодовых точек для чистки текста сообщений.
/// </summary>
internal static class CodePointExtensions
{
/// <summary>
/// Входит ли кодовая точка в эмодзи-диапазоны прототипа (pipeline.py _EMOJI_RE L137–145).
/// </summary>
/// <param name="codePoint">Кодовая точка (BMP или доп. плоскость).</param>
/// <returns>True — декоративный символ, подлежащий удалению.</returns>
public static bool IsEmojiCodePoint(this int codePoint) =>
codePoint is >= 0x1F000 and <= 0x1FAFF
or >= 0x2600 and <= 0x27BF
or >= 0x2B00 and <= 0x2BFF
or 0xFE0F;
}
@@ -208,7 +208,7 @@ public static class MessageTextCleaner
continue;
}
if (IsEmojiCodePoint(codePoint))
if (codePoint.IsEmojiCodePoint())
{
index += length - 1;
continue;
@@ -245,17 +245,6 @@ public static class MessageTextCleaner
return current;
}
// Входит ли кодовая точка в эмодзи-диапазоны прототипа (L137–145).
// codePoint: Кодовая точка (BMP или доп. плоскость).
// Возвращает: True — декоративный символ, подлежащий удалению.
private static bool IsEmojiCodePoint(int codePoint)
{
return codePoint is >= 0x1F000 and <= 0x1FAFF
or >= 0x2600 and <= 0x27BF
or >= 0x2B00 and <= 0x2BFF
or 0xFE0F;
}
// Обрезка по границе последнего переноса/пробела в первых limit кодовых точках
// (прототип L184–192): выбирается перенос (или пробел), если он после середины лимита; иначе режем жёстко.
// value: Текст длиннее лимита.
@@ -0,0 +1,33 @@
namespace Deal.Modules.Pipeline.Application.Parse;
/// <summary>
/// Расширения строк для разбора текста сообщений.
/// </summary>
internal static class StringExtensions
{
// Служебные строки/фразы футеров агрегаторов: «суть» с ними — не структура, а шум (python L288–291).
private static readonly string[] FooterHintsArray =
{
"откликнуться через", "runello", "больше вакансий", "teletype", "при отклике укажите",
"больше заявок", "узнать подробнее", "написать в лс", "пишите в лс",
};
/// <summary>
/// Содержит ли текст служебный футер-хинт (python L267/L275: сравнение с casefold-текстом).
/// </summary>
/// <param name="text">Текст (в любом регистре; null трактуется как пустая строка).</param>
/// <returns>True — текст похож на футер агрегатора/служебную строку.</returns>
public static bool ContainsFooterHint(this string text)
{
string lower = text.ToLowerInvariant();
foreach (string hint in FooterHintsArray)
{
if (lower.Contains(hint, StringComparison.Ordinal))
{
return true;
}
}
return false;
}
}
@@ -10,7 +10,7 @@ namespace Deal.Modules.Pipeline.Application.Parse;
/// Карточка всегда собирается из одних и тех же блоков — Компания → Формат → О задаче → Требования →
/// Будет плюсом → Условия (1:1 с cardPrompt); недостающие блоки пропускаются. Если структурированных полей нет
/// (<see cref="ParsedCardContent.Summary"/> от локального/старого разбора) — суть сохраняется как есть, но
/// отбрасывается, когда похожа на служебный футер агрегаторов (<see cref="FooterHints"/>); тогда «О задаче»
/// отбрасывается, когда похожа на служебный футер агрегаторов (<see cref="StringExtensions.ContainsFooterHint(string)"/>); тогда «О задаче»
/// собирается из содержательных строк текста (<see cref="LocalSummary"/>, с префиксом «О задаче: »).
/// </remarks>
public static class SummaryComposer
@@ -43,13 +43,6 @@ public static class SummaryComposer
"вакансия", "вакансию", "фриланс",
};
// Служебные строки/фразы футеров агрегаторов: «суть» с ними — не структура, а шум (python L288–291).
private static readonly string[] FooterHintsArray =
{
"откликнуться через", "runello", "больше вакансий", "teletype", "при отклике укажите",
"больше заявок", "узнать подробнее", "написать в лс", "пишите в лс",
};
/// <summary>
/// Собирает «О заявке» из структурированных полей либо текста (python compose_summary L225–284).
/// </summary>
@@ -105,7 +98,7 @@ public static class SummaryComposer
// Структурированных полей нет. «summary» разбора часто является копией исходника/шумом — если в нём есть
// футеры/хэштеги-мусор, не используем его (python L264–268).
string legacy = MessageTextCleaner.CleanShort(source.Summary);
if (legacy.Length > 0 && !ContainsFooterHint(legacy))
if (legacy.Length > 0 && !legacy.ContainsFooterHint())
{
return legacy;
}
@@ -121,7 +114,7 @@ public static class SummaryComposer
}
string lower = line.ToLowerInvariant();
if (line.StartsWith('#') || lower.StartsWith("**#") || ContainsFooterHint(lower))
if (line.StartsWith('#') || lower.StartsWith("**#") || lower.ContainsFooterHint())
{
continue;
}
@@ -205,21 +198,4 @@ public static class SummaryComposer
return result;
}
// Содержит ли текст служебный футер-хинт (python L267/L275: сравнение с casefold-текстом).
// text: Текст (в любом регистре).
// Возвращает: True — текст похож на футер агрегатора/служебную строку.
private static bool ContainsFooterHint(string text)
{
string lower = text.ToLowerInvariant();
foreach (string hint in FooterHintsArray)
{
if (lower.Contains(hint, StringComparison.Ordinal))
{
return true;
}
}
return false;
}
}