mirror of
https://gitverse.ru/kpa39l/vesti.git
synced 2026-09-29 01:50:03 +00:00
145 lines
9.7 KiB
Python
145 lines
9.7 KiB
Python
# Классификатор: словари ключевых слов по направлениям (MUST: словарный фильтр первым).
|
|
# Направления расширяемы; для прототипа — linux (основное) + общие.
|
|
# Канонический список направлений (AGENT.MD, веб-форма fan-out):
|
|
DIRECTIONS_CANON = ["linux", "tech", "politics", "games", "electronics", "llm"]
|
|
|
|
DIRECTIONS = {
|
|
"linux": {
|
|
"keywords": [
|
|
"linux", "kernel", "gnome", "kde", "distro", "distribution", "ubuntu", "fedora",
|
|
"debian", "arch", "manjaro", "mint", "opensuse", "xfce", "wayland", "xorg",
|
|
"systemd", "apt", "dnf", "pacman", "aur", "snap", "flatpak", "bash", "zsh",
|
|
"terminal", "shell", "foss", "open source", "opensource", "поверх", "ядро",
|
|
"дистрибутив", "пакетный менеджер", "реестр пакетов", "libreoffice", "gimp",
|
|
"вокал", "linux-", "tux", "posix", "unix", "gnu", "kde plasma", "gtk", "qt",
|
|
"docker", "podman", "k8s", "kubernetes", "контейнер", "образ", "контейнеризация",
|
|
"self-host", "selfhost", "homelab", "сервер", "vps", "devops", "инфраструктура",
|
|
"nginx", "postgres", "sqlite", "redis", "grafana", "prometheus", "ansible",
|
|
"terraform", "caddy", "docker-compose", "compose", "proxy", "vpn", "wireguard",
|
|
"ssh", "sftp", "tmux", "vim", "neovim", "emacs", "git", "github", "gitlab",
|
|
"open source", "клонирование", "репозиторий", "пакет", "сборка", "компиляция",
|
|
"cli", "консоль", "командная строка", "python", "c++", "rust", "go",
|
|
"golang", "golang", "programming", "программирование", "разработка", "код", "скрипт",
|
|
"автоматизация", "юникс", "линукс", "пентинг", "security", "безопасность",
|
|
"шифрование", "gnupg", "firewall", "iptables", "nftables", "selinux", "apparmor",
|
|
"btrfs", "ext4", "zfs", "файловая система", "lvm", "raid", "ssd", "nvme",
|
|
"железо", "драйвер", "фирмware", "прошивка", "bios", "uefi", "grub",
|
|
"безголовый", "headless", "микросервис", "api", "rest", "http", "tcp", "udp",
|
|
],
|
|
# дополнительные точные фразы (более сильные)
|
|
"phrases": ["linux", "ubuntu", "debian", "arch linux", "fedora", "kernel", "gnome", "kde"],
|
|
},
|
|
"tech": {
|
|
"keywords": [
|
|
# dev (бывший отдельный; разработка/программирование — tech)
|
|
"разработка", "программирование", "код", "python", "golang", "golang", "rust", "javascript",
|
|
"typescript", "node", "react", "vue", "next.js", "docker", "kubernetes", "api",
|
|
"backend", "frontend", "фреймворк", "алгоритм", "open source", "github", "gitlab",
|
|
"ci/cd", "тестирование", "деплой", "микросервисы", "база данных", "sql", "nosql",
|
|
"инженер", "разработчик", "программист", "компилятор", "интерпретатор", "ide",
|
|
"дебаггер", "рефакторинг", "собеседование", "технологии",
|
|
# tech (бывший)
|
|
"технологии", "гаджет", "смартфон", "процессор", "чип", "android", "iphone",
|
|
"apple", "google", "microsoft", "windows", "железо", "компьютер", "ноутбук",
|
|
"сервер", "storage", "хранилище", "облако", "cloud", "интернет вещей", "iot",
|
|
"робот", "робототехника", "экран", "дисплей", "батарея", "зарядка", "usb-c",
|
|
"хакер", "уязвимость", "эксплойт", "zero-day", "шифрование", "приватность",
|
|
],
|
|
"phrases": ["смартфон", "гаджет", "чип", "apple", "технологии", "программирование", "разработка", "software", "developer", "code"],
|
|
},
|
|
"politics": {
|
|
"keywords": [
|
|
"политика", "президент", "выборы", "закон", "законопроект", "госдума", "кремль",
|
|
"дума", "правительство", "министр", "санкции", "война", "мир", "конфликт",
|
|
"армия", "нато", "украина", "россия", "сша", "китай", "евросоюз", "телеграм-канал",
|
|
"депутат", "партия", "референдум", "голосование", "политик", "геополитика",
|
|
"международный", "амбассадор", "посол", "договор", "переговоры",
|
|
],
|
|
"phrases": ["политика", "выборы", "президент", "госдума", "санкции", "война"],
|
|
},
|
|
"games": {
|
|
"keywords": [
|
|
"игра", "игры", "гейминг", "gaming", "steam", "playstation", "xbox", "nintendo",
|
|
"геймпад", "игровая консоль", "видеоигра", "мморпг", "шутер", "стратегия",
|
|
"симулятор", "инди-игра", "киберспорт", "esports", "виртуальная реальность",
|
|
"vr", "игровой движок", "unity", "unreal", "геймер",
|
|
],
|
|
"phrases": ["игра", "гейминг", "steam", "gaming"],
|
|
},
|
|
"electronics": {
|
|
"keywords": [
|
|
"электроника", "микроконтроллер", "arduino", "raspberry", "esp32", "esp8266",
|
|
"пайка", "схема", "плата", "микросхема", "транзистор", "резистор", "конденсатор",
|
|
"осциллограф", "мультиметр", "радио", "антенна", "sdr", "fpga", "промышленность",
|
|
"электронный", "компонент", "датчик", "силовой", "реле",
|
|
],
|
|
"phrases": ["arduino", "raspberry", "fpga", "микроконтроллер"],
|
|
},
|
|
"llm": {
|
|
"keywords": [
|
|
"ии", "ai", "llm", "gpt", "нейросеть", "нейронная сеть", "deep learning",
|
|
"машинное обучение", "ml", "модель", "обучение модели", "инференс", "файнтюнинг",
|
|
"fine-tuning", "rlhf", "агент", "агенты", "генеративный", "стабильная диффузия",
|
|
"stable diffusion", "midjourney", "чат-бот", "chatbot", "ассистент", "токен",
|
|
"трансформер", "embedding", "векторная база", "rag", "pipeline", "инференс",
|
|
"openai", "антропик", "claude", "gemini", "olama", "qwen",
|
|
],
|
|
"phrases": ["нейросеть", "llm", "чат-бот", "openai", "claude"],
|
|
},
|
|
}
|
|
|
|
import re
|
|
|
|
# Направление по умолчанию, если ничего не подошло
|
|
DEFAULT_DIRECTION = "linux" # прототип: все посты про linux окружение
|
|
|
|
# Правила по источнику (slug в sources.yaml): принудительное направление
|
|
# для всего контента источника, независимо от словаря/LLM.
|
|
# LWN — технологическое СМИ: весь контент → tech.
|
|
SOURCE_RULES: dict[str, list[str]] = {
|
|
"lwn": ["tech"],
|
|
}
|
|
|
|
|
|
def source_directions(source_slug: str | None) -> list[str]:
|
|
"""Направления, принудительно назначенные источнику (SOURCE_RULES)."""
|
|
return list(SOURCE_RULES.get(source_slug or "", []))
|
|
|
|
|
|
def find_directions_all(text: str, source_slug: str | None = None) -> list[str]:
|
|
"""Возвращает ВСЕ направления, которым соответствует текст (для мультинаправлений/fan-out).
|
|
|
|
Если для источника задано правило SOURCE_RULES — возвращает его направления
|
|
(принудительно), иначе — словарные попадания.
|
|
"""
|
|
forced = source_directions(source_slug)
|
|
if forced:
|
|
return forced
|
|
text_low = (text or "").lower()
|
|
hits = set()
|
|
for direction, cfg in DIRECTIONS.items():
|
|
# точные фразы
|
|
if any(p.lower() in text_low for p in cfg.get("phrases", [])):
|
|
hits.add(direction)
|
|
continue
|
|
# ключевые слова (по слову, re.search с границами)
|
|
for kw in cfg.get("keywords", []):
|
|
kw_low = kw.lower()
|
|
if re.search(rf"\b{re.escape(kw_low)}\b", text_low):
|
|
hits.add(direction)
|
|
break
|
|
return list(hits)
|
|
|
|
|
|
def find_direction(text: str, source_slug: str | None = None) -> str | None:
|
|
"""Возвращает направление: правило источника (SOURCE_RULES) или словарное.
|
|
|
|
Принудительное правило источника имеет приоритет над словарём.
|
|
"""
|
|
forced = source_directions(source_slug)
|
|
if forced:
|
|
return forced[0]
|
|
dirs = find_directions_all(text)
|
|
if dirs:
|
|
return dirs[0]
|
|
return None |