feat: трёхэтапный фильтр мусора — этапы 1-2 (0 токенов) в classify.py
This commit is contained in:
@@ -53,6 +53,14 @@ def set_classify_failed(doc_id, error):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def set_classify_garbage(doc_id, reason=""):
|
||||||
|
"""Mark document as garbage (Stage 1-2 filter, no LLM call)."""
|
||||||
|
return execute(
|
||||||
|
"UPDATE documents SET doc_type='garbage', classify_status='garbage', error_message=%s WHERE id=%s",
|
||||||
|
(f"garbage: {reason}", doc_id),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def list_pending(batch_id):
|
def list_pending(batch_id):
|
||||||
"""Documents waiting for classification."""
|
"""Documents waiting for classification."""
|
||||||
return query(
|
return query(
|
||||||
|
|||||||
@@ -27,6 +27,31 @@ LLM_URL = "https://api.aillm.ru/v1/chat/completions"
|
|||||||
LLM_KEY = os.environ.get("LLM_KEY") or os.environ.get("LLM_API_KEY", "")
|
LLM_KEY = os.environ.get("LLM_KEY") or os.environ.get("LLM_API_KEY", "")
|
||||||
LLM_MODEL = "gpt-oss-120b"
|
LLM_MODEL = "gpt-oss-120b"
|
||||||
|
|
||||||
|
# ── Garbage filter (Stage 1: filename regex) ────────────────────
|
||||||
|
_GARBAGE_FILENAME_RE = re.compile(
|
||||||
|
r'(сч[её]т|акт|плат[её]ж|УПД|сверк|инвойс|invoice|payment|act)',
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── Garbage filter (Stage 2: header keywords) ───────────────────
|
||||||
|
_GARBAGE_HEADER_MARKERS = [
|
||||||
|
'СЧЕТ-ФАКТУРА', 'СЧЕТ НА ОПЛАТУ', 'АКТ СВЕРКИ',
|
||||||
|
'АКТ ОКАЗАННЫХ УСЛУГ', 'АКТ ВЫПОЛНЕННЫХ РАБОТ',
|
||||||
|
'ПЛАТЁЖНОЕ ПОРУЧЕНИЕ', 'УНИВЕРСАЛЬНЫЙ ПЕРЕДАТОЧНЫЙ',
|
||||||
|
'УПД', 'ПЛАТЕЖНОЕ ПОРУЧЕНИЕ',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _is_garbage_by_filename(filename: str) -> bool:
|
||||||
|
"""Stage 1: regex по имени файла — быстро, 0 токенов."""
|
||||||
|
return bool(_GARBAGE_FILENAME_RE.search(filename))
|
||||||
|
|
||||||
|
|
||||||
|
def _is_garbage_by_header(text: str) -> bool:
|
||||||
|
"""Stage 2: ключевые слова в первых 2KB текста — быстро, 0 токенов."""
|
||||||
|
header = text[:2000].upper()
|
||||||
|
return any(marker in header for marker in _GARBAGE_HEADER_MARKERS)
|
||||||
|
|
||||||
|
|
||||||
def _call_llm_classify(header_text):
|
def _call_llm_classify(header_text):
|
||||||
"""
|
"""
|
||||||
@@ -70,9 +95,20 @@ def classify_batch(batch_id):
|
|||||||
failed = 0
|
failed = 0
|
||||||
|
|
||||||
def _classify_one(doc):
|
def _classify_one(doc):
|
||||||
"""Классифицировать один документ: выжимка → LLM → сохранить результат."""
|
"""Классифицировать один документ: фильтр → выжимка → LLM → сохранить."""
|
||||||
try:
|
try:
|
||||||
|
# Stage 1: garbage by filename (0 tokens)
|
||||||
|
if _is_garbage_by_filename(doc["filename"]):
|
||||||
|
db_docs.set_classify_garbage(doc["id"], "filename_regex")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Stage 2: garbage by header keywords (0 tokens)
|
||||||
text = _smart_extract(doc["elements_json"])
|
text = _smart_extract(doc["elements_json"])
|
||||||
|
if _is_garbage_by_header(text):
|
||||||
|
db_docs.set_classify_garbage(doc["id"], "header_keywords")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Stage 3: LLM classification (only for remaining)
|
||||||
result, raw = _call_llm_classify(text)
|
result, raw = _call_llm_classify(text)
|
||||||
db_docs.set_classification(
|
db_docs.set_classification(
|
||||||
doc["id"],
|
doc["id"],
|
||||||
|
|||||||
Reference in New Issue
Block a user