diff --git a/site/extractor.py b/site/extractor.py new file mode 100644 index 0000000..e5c8100 --- /dev/null +++ b/site/extractor.py @@ -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], + }