103 lines
4.0 KiB
Python
103 lines
4.0 KiB
Python
"""Эндпоинты DTC: расшифровка и сохранение кодов ошибок.
|
|
|
|
POST /api/v1/dtc/decode — расшифровка кодов
|
|
POST /api/v1/dtc/upload — приём скана ошибок
|
|
"""
|
|
|
|
import logging
|
|
from pathlib import Path
|
|
|
|
from flask import jsonify, request
|
|
|
|
from api.db import Database
|
|
|
|
logger = logging.getLogger("elmer.dtc")
|
|
|
|
# Загрузка справочника DTC
|
|
_DTC_CODES: dict[str, str] = {}
|
|
_DTC_PATH = Path(__file__).parent.parent / "doc" / "dtc_codes.txt"
|
|
|
|
|
|
def _load_dtc_dict() -> dict[str, str]:
|
|
"""Загрузить справочник DTC-кодов из doc/dtc_codes.txt."""
|
|
global _DTC_CODES
|
|
if _DTC_CODES:
|
|
return _DTC_CODES
|
|
if _DTC_PATH.exists():
|
|
for line in _DTC_PATH.read_text(encoding="utf-8").splitlines():
|
|
line = line.strip()
|
|
if "=" in line and not line.startswith("#"):
|
|
code, desc = line.split("=", 1)
|
|
_DTC_CODES[code.strip()] = desc.strip()
|
|
return _DTC_CODES
|
|
|
|
|
|
def register(app):
|
|
"""Регистрирует DTC-эндпоинты на Flask-приложении."""
|
|
|
|
@app.route("/api/v1/dtc/decode", methods=["POST"])
|
|
def dtc_decode():
|
|
"""Расшифровка кодов DTC из справочника doc/dtc_codes.txt.
|
|
|
|
Принимает: {"codes": ["P0301", "P0302"]}
|
|
Возвращает: {"codes": [{"code": "P0301", "desc": "..."}]}
|
|
Неизвестные коды возвращаются как есть — справочник неполный.
|
|
"""
|
|
data = request.get_json(silent=True) or {}
|
|
codes = data.get("codes", [])
|
|
if not codes:
|
|
return jsonify({"codes": []})
|
|
dtc = _load_dtc_dict()
|
|
decoded = [{"code": c.strip().upper(), "desc": dtc.get(c.strip().upper(), c)} for c in codes]
|
|
return jsonify({"codes": decoded})
|
|
|
|
@app.route("/api/v1/dtc/upload", methods=["POST"])
|
|
def dtc_upload():
|
|
"""Приём кодов ошибок (без полной диагностики, только Ошибки).
|
|
|
|
Идемпотентность: если client шлёт request_id, сервер:
|
|
1. Проверяет кэш — если есть, возвращает его (LLM не вызывается)
|
|
2. Если нет — сохраняет, кэширует ответ
|
|
Это предотвращает дубликаты при повторных отправках (например, 499).
|
|
"""
|
|
data = request.get_json(silent=True)
|
|
if not data:
|
|
return jsonify({"error": "empty"}), 400
|
|
|
|
codes = data.get("dtc_codes", [])
|
|
client_info = data.get("client_info", {})
|
|
client_info["client_ip"] = request.remote_addr
|
|
|
|
# Идемпотентность
|
|
request_id = (data.get("request_id") or "").strip()
|
|
if request_id:
|
|
with Database() as db:
|
|
cached = db.get_cached_response(request_id)
|
|
if cached is not None:
|
|
return jsonify(cached), 200
|
|
|
|
try:
|
|
with Database() as db:
|
|
db.save_dtc_scan(client_info, codes)
|
|
except Exception as e:
|
|
logger.error(f"DTC upload failed: {e}")
|
|
|
|
dtc = _load_dtc_dict()
|
|
decoded = [{"code": c, "desc": dtc.get(c.strip().upper(), c)} for c in codes]
|
|
response = {"codes": decoded, "count": len(decoded)}
|
|
|
|
# Кэш для идемпотентности
|
|
if request_id:
|
|
try:
|
|
with Database() as db:
|
|
db.save_session(
|
|
client_info=client_info,
|
|
responses=[{"decoded": f"DTC stored: {c}"} for c in codes],
|
|
request_id=request_id,
|
|
response_json=response,
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"DTC idempotency cache failed: {e}")
|
|
|
|
return jsonify(response)
|