104 lines
4.2 KiB
Python
104 lines
4.2 KiB
Python
"""Эндпоинты для толстого клиента: скрипты и батчевая загрузка.
|
|
|
|
GET /api/v1/script — выдача скрипта диагностики
|
|
POST /api/v1/session/upload — приём батча, LLM-анализ, возврат диагноза
|
|
"""
|
|
|
|
import logging
|
|
from web.script_builder import build_default_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
|
|
return jsonify(build_default_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"),
|
|
)
|
|
answer = diagnoser.diagnose(SYSTEM_PROMPT, _build_diagnosis_prompt(parsed))
|
|
|
|
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"],
|
|
}
|