"""Эндпоинты для толстого клиента: скрипты и батчевая загрузка. GET /api/v1/script — выдача скрипта диагностики POST /api/v1/session/upload — приём батча, LLM-анализ, возврат диагноза """ import logging from web.script_builder import build_default_script, build_full_script from web.script_parser import parse_batch, format_no_llm logger = logging.getLogger("elmer.script") def _build_diagnosis_prompt(data: dict) -> str: """Строит промпт для LLM из распарсенных данных.""" parts = ["## Данные диагностики\n"] if data["vin"]: parts.append(f"**VIN:** {data['vin']}") if data["dtc_stored"]: parts.append(f"\n**Сохранённые ошибки (mode 03):** {', '.join(data['dtc_stored'])}") if data["dtc_pending"]: parts.append(f"**Ожидающие ошибки (mode 07):** {', '.join(data['dtc_pending'])}") if data["parameters"]: parts.append("\n**Параметры в реальном времени:**") for p in data["parameters"]: parts.append(f"- {p['name']}: {p['value']}") if not data["vin"] and not data["dtc_stored"] and not data["parameters"]: parts.append("\n(данные не распознаны)") parts.append("\n**Сырые ответы ЭБУ:**") parts.extend(data["raw_log"]) parts.append("\n---") parts.append("## Запрос на анализ") parts.append( "Дай ГЛУБОКИЙ, РАЗВЁРНУТЫЙ анализ на основе этих данных. " "Не ограничивайся кратким резюме — мне нужен полный технический разбор.\n" "1. Разбери КАЖДУЮ ошибку: все возможные причины, от частых к редким.\n" "2. Проанализируй КАЖДЫЙ параметр: норма/отклонение, на что влияет.\n" "3. Найди ВЗАИМОСВЯЗИ между ошибками и параметрами.\n" "4. Предложи КОНКРЕТНЫЙ план проверок: что сначала, что потом, как проверять.\n" "5. Если данных мало — скажи какие PID'ы досчитать и зачем.\n" "6. Дай оценки уверенности в ПРОЦЕНТАХ для каждой версии." ) return "\n".join(parts) def register(app): """Регистрирует эндпоинты скриптов на Flask-приложении.""" @app.route("/api/v1/script", methods=["GET"]) def get_script(): from flask import jsonify, request mode = request.args.get("mode", "full") script = build_full_script() if mode == "full" else build_default_script() return jsonify(script) @app.route("/api/v1/session/upload", methods=["POST"]) def upload_session(): from flask import request, jsonify from elmer.config import load from elmer.diagnose import Diagnoser from elmer.prompts import SYSTEM_PROMPT data = request.get_json(silent=True) if not data or "responses" not in data: return jsonify({"error": "missing 'responses'"}), 400 responses = data["responses"] logger.info(f"Upload: {len(responses)} responses") parsed = parse_batch(responses) cfg = load() api_key = cfg["llm"]["api_key"] if not api_key: return jsonify({ "diagnosis": format_no_llm(parsed), "parsed": _summary(parsed), }) diagnoser = Diagnoser( api_key=api_key, model=cfg["llm"].get("model", "gpt-oss-120b"), base_url=cfg["llm"].get("base_url", "https://api.aillm.ru/v1"), ) try: answer = diagnoser.diagnose(SYSTEM_PROMPT, _build_diagnosis_prompt(parsed)) except Exception as e: logger.warning(f"LLM failed: {e}") answer = format_no_llm(parsed) + f"\n\n(LLM недоступен: {e})" return jsonify({ "diagnosis": answer, "parsed": _summary(parsed), }) def _summary(p: dict) -> dict: return { "vin": p["vin"], "dtc_stored": p["dtc_stored"], "dtc_pending": p["dtc_pending"], "parameters": p["parameters"], }