Files
elmer/api/dtc.py
T

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)