120 lines
5.1 KiB
Python
120 lines
5.1 KiB
Python
"""
|
|
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, custom_prompt: str = None) -> dict:
|
|
"""
|
|
Извлечь строки спецификации из текста документа через LLM.
|
|
|
|
Args:
|
|
parsed_text: текст документа (выдача textify.py)
|
|
custom_prompt: свой промпт (если None — используется DEFAULT_PROMPT)
|
|
|
|
Returns:
|
|
{"rows": [...], "unresolved": [...]} или {"error": "..."}
|
|
"""
|
|
|
|
prompt = custom_prompt or DEFAULT_PROMPT
|
|
prompt = prompt.replace("{parsed_text}", parsed_text)
|
|
"""
|
|
extractor.py — Извлечение строк спецификации из текста документа.
|
|
|
|
Берёт распарсенный текст (parsed_text из documents), отправляет LLM,
|
|
получает структурированный список строк спецификации.
|
|
|
|
Не зависит от parser.py, textify.py, upload.py.
|
|
Зависит только от llm_client.py.
|
|
"""
|
|
|
|
import json
|
|
import llm_client
|
|
|
|
# ── Промпт по умолчанию ─────────────────────────────────────────
|
|
# {parsed_text} будет заменён на текст документа
|
|
|
|
DEFAULT_PROMPT = """Ты — анализатор договоров. Ниже текст спецификации услуг из договора облачного провайдера.
|
|
|
|
Найди ВСЕ таблицы со списком услуг. Извлеки каждую строку в 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}
|
|
---
|
|
"""
|
|
|
|
|
|
def extract(parsed_text: str, custom_prompt: str = None) -> dict:
|
|
"""
|
|
Извлечь строки спецификации из текста документа через LLM.
|
|
|
|
Args:
|
|
parsed_text: текст документа (выдача textify.py)
|
|
custom_prompt: свой промпт (если None — DEFAULT_PROMPT).
|
|
Должен содержать {parsed_text} для подстановки текста.
|
|
|
|
Returns:
|
|
{"rows": [...], "unresolved": [...]} или {"error": "..."}
|
|
"""
|
|
prompt = (custom_prompt or DEFAULT_PROMPT).replace("{parsed_text}", 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],
|
|
}
|