feat(extractor): extractor.py — извлечение строк спецификации через LLM
This commit is contained in:
@@ -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],
|
||||
}
|
||||
Reference in New Issue
Block a user