feat(extractor): extractor.py — извлечение строк спецификации через LLM

This commit is contained in:
2026-06-14 08:06:01 +04:00
parent 04daa35b56
commit 33542d3649
+88
View File
@@ -0,0 +1,88 @@
"""
extractor.py — Извлечение строк спецификации из текста документа.
Берёт распарсенный текст (parsed_text из documents), отправляет LLM,
получает структурированный список строк спецификации.
Не зависит от parser.py, textify.py, upload.py.
Зависит только от llm_client.py.
"""
import json
import llm_client
def extract(parsed_text: str) -> dict:
"""
Извлечь строки спецификации из текста документа через LLM.
Args:
parsed_text: текст документа (выдача textify.py)
Returns:
{"rows": [{row_num, name, price, qty, sum, date_start}, ...], "unresolved": [...]}
или
{"error": "...", "raw_response": "..."}
"""
# ── Промпт ──────────────────────────────────────────────────
prompt = f"""Ты — анализатор договоров. Ниже текст спецификации услуг из договора облачного провайдера.
Найди ВСЕ таблицы со списком услуг. Извлеки каждую строку в JSON-массив.
Для каждой строки верни:
- row_num: номер по порядку (целое число)
- name: полное наименование услуги (весь текст из ячейки)
- price: цена за единицу в рублях (число, из колонки "Цена")
- qty: количество/объём (число, из колонки "Объем")
- sum: итоговая сумма (число, из колонки "Сумма")
- date_start: дата начала оказания (строка YYYY-MM-DD)
ПРАВИЛА:
1. Пропускай итоговые строки ("Итого...") и строки с подписями.
2. Если ячейка пустая — ставь null.
3. Если не можешь определить значение — ставь null и добавь в unresolved.
4. Верни ТОЛЬКО JSON, без пояснений.
Пример ответа:
{{"rows":[{{"row_num":1,"name":"Аренда стойко-места","price":213905.44,"qty":3,"sum":641716.32,"date_start":"2026-04-01"}}],"unresolved":[]}}
Текст документа:
---
{parsed_text}
---
"""
# ── Вызов LLM ────────────────────────────────────────────────
result = llm_client.ask(prompt, max_tokens=8000)
if "error" in result:
return {"error": result["error"]}
# ── Парсинг ответа ───────────────────────────────────────────
raw_text = result.get("text", "")
try:
# Найти JSON в ответе (может быть обёрнут в ```json ... ```)
json_text = raw_text
if "```json" in json_text:
json_text = json_text.split("```json")[1].split("```")[0]
elif "```" in json_text:
json_text = json_text.split("```")[1].split("```")[0]
data = json.loads(json_text.strip())
rows = data.get("rows", [])
unresolved = data.get("unresolved", [])
return {
"rows": rows,
"unresolved": unresolved,
"model": result.get("model", ""),
"usage": result.get("usage", {}),
}
except (json.JSONDecodeError, IndexError, KeyError) as e:
return {
"error": f"parse_failed: {e}",
"raw_response": raw_text[:1000],
}