Files
omni-parse/omni/taxonomy.py

92 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""Типизация записей: не «категория источника», а тип самого поста.
Категория отвечает «откуда», тип — «что именно»: слив, триал, софт, промо,
перепродажа, CVE, релиз, гайд, датасет, модель, вакансия, цена, вопрос, новость.
Одна запись может иметь несколько типов, порядок — по весу совпадений.
"""
import re
# (key, label, weight, [regex, ...])
RULES = [
("leak", "слив", 10, [
r"\bслив\w*", r"утечк\w+", r"\bleak(ed|s)?\b", r"\bdump(ed)?\b", r"база\s+(данных|слит)",
r"\bcombo(list)?\b", r"stealer\s*log", r"\bdb\s+dump\b", r"пасс\w*\s+от\s+",
]),
("trial", "триал", 9, [
r"\btrial\b", r"триал\w*", r"пробн\w+\s+период", r"free\s+(credits?|tier|plan)",
r"бесплатн\w+\s+(кредит|токен|доступ|период|тариф)", r"\bfreebies?\b", r"щедр\w+\s+лимит",
r"\$\d+\s*(free|бесплат)", r"no\s+credit\s+card",
]),
("tool", "софт", 8, [
r"\bсофт\w*", r"\btool(s|kit)?\b", r"скрипт\w*", r"\bparser\b", r"\bчекер\b", r"\bбот\w*",
r"\bcrawler\b", r"\bscraper\b", r"\bautomation\b", r"автоматизац\w+", r"\bcli\b",
r"\bsdk\b", r"open[\s-]?source\s+(tool|project)",
]),
("promo", "промо", 7, [
r"промокод\w*", r"\bpromo\w*", r"\bcoupon\b", r"скидк\w+", r"\bdiscount\b", r"giveaway",
r"раздач\w+", r"\bairdrop\b", r"бонус\w*\s+(код|за\s+регистрац)", r"-?\d{1,2}%\s*off",
]),
("resale", "перепродажа", 7, [
r"\bпродам\b", r"\bпродаж\w+", r"\bresell\w*", r"\bв\s+аренду\b", r"\bкуплю\b",
r"\bsell(ing)?\s+(account|key|access)", r"перепрод\w+", r"\bоптом\b", r"\bв\s+наличии\b.*\$",
]),
("cve", "уязвимость", 10, [
r"\bCVE-\d{4}-\d{3,7}\b", r"\bvulnerab\w+", r"уязвим\w+", r"\bRCE\b", r"\bXSS\b",
r"\bSQLi?\b", r"\bexploit\w*", r"\b0day\b", r"\bpatch\w*\s+tuesday", r"security\s+advisory",
]),
("release", "релиз", 6, [
r"\brelease[sd]?\b", r"\bрелиз\w*", r"\bchangelog\b", r"\bv\d+\.\d+(\.\d+)?\b",
r"вышл\w+\s+версия", r"\bnow\s+available\b", r"\bGA\b", r"\bpreview\s+build\b",
]),
("guide", "гайд", 5, [
r"\bгайд\w*", r"\btutorial\b", r"\bhow[\s-]to\b", r"инструкц\w+", r"\bруководств\w+",
r"\bкак\s+(сделать|настроить|поднять|получить)", r"\bwalkthrough\b", r"\bcheat\s*sheet\b",
]),
("dataset", "датасет", 6, [
r"\bdataset\w*", r"датасет\w*", r"\bcorpus\b", r"\bbenchmark\s+suite\b", r"\bannotated\b",
]),
("model", "модель", 5, [
r"\bweights\b", r"\bcheckpoint\w*", r"\.gguf\b", r"\.safetensors\b", r"\bllm\b",
r"\bмодел\w+\s+(вышла|обучен| weights)", r"\bquantiz\w+", r"\bfine-?tun\w+",
]),
("job", "вакансия", 6, [
r"\bваканси\w+", r"\bhiring\b", r"\bjob\s+(offer|post)", r"\bremote\b.*\$",
r"\bsalary\b", r"\$\d+\s*/\s*(hr|hour)", r"\bищу\s+работу\b", r"\bfor\s+hire\b",
]),
("price", "цена", 4, [
r"\bцен[аы]\b", r"\bpricing\b", r"\bтариф\w*", r"\$\d", r"\d+\s*₽", r"\d+\s*€",
r"\bper\s+(month|year|1m)\b", r"/\s*1m\s+tokens",
]),
("question", "вопрос", 3, [
r"\?\s*$", r"^\s*(как|почему|что|где|кто|можно\s+ли)\b", r"\bhelp\b", r"\bподскажите\b",
]),
]
COMPILED = [(k, l, w, [re.compile(p, re.I | re.U) for p in pats]) for k, l, w, pats in RULES]
LABELS = {k: l for k, l, _, _ in RULES}
LABELS["news"] = "новость"
KEYS = [k for k, _, _, _ in RULES]
def classify(title, text, limit=3):
"""Список типов записи по убыванию веса. Если ничего не сработало — ['news']."""
t = (title or "") + "\n" + (text or "")[:2000]
if not t.strip():
return ["news"]
hits = []
for key, label, w, pats in COMPILED:
n = 0
for p in pats:
if p.search(t):
n += 1
if n:
hits.append((w + n, key))
if not hits:
return ["news"]
hits.sort(reverse=True)
return [k for _, k in hits[:limit]]
def labels(keys):
return [LABELS.get(k, k) for k in keys or []]