refactor: разбил routes/db/protocol на отдельные модули

This commit is contained in:
Repinoid
2026-06-06 07:16:51 +04:00
parent d2f42e99bb
commit 7aaebf8481
9 changed files with 291 additions and 357 deletions
+10 -134
View File
@@ -1,12 +1,15 @@
"""SQLite — сохранение сессий диагностики. """SQLite — сохранение сессий диагностики.
Схема: Таблица sessions (35+ колонок):
cars — VIN, марка, модель, год, двигатель client_ip, real_ip, user_agent, content_length, created_at
diagnostic_tokens — id (PK), car_id (FK), created_at phone_model, phone_maker, android_version, android_sdk, app_version
llm_messages — token_id (FK), role, content, timestamp android_id, device_uuid, phone_lang, phone_tz, phone_display
ecu_parameters — token_id (FK), pid_code, value, unit, timestamp elm_mac, elm_bt_name, obd_protocol
dtc_codes — token_id (FK), code, description, status vin, dtc_count, pid_count
sessions — сводная таблица всех сессий (клиент, ELM, авто, LLM) duration_ms, response_count, error_count, retry_count, timeout_count
script_mode, transport, mock_mode, car_info
diagnosis_text, diagnosis_len, llm_model, llm_duration_ms, llm_success
raw_responses, request_id, response_json
""" """
import json import json
@@ -38,49 +41,6 @@ class Database:
def _init_schema(self): def _init_schema(self):
self.conn.executescript(""" self.conn.executescript("""
CREATE TABLE IF NOT EXISTS cars (
id INTEGER PRIMARY KEY AUTOINCREMENT,
vin TEXT NOT NULL UNIQUE,
make TEXT,
model TEXT,
year INTEGER,
engine TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS diagnostic_tokens (
id INTEGER PRIMARY KEY AUTOINCREMENT,
car_id INTEGER NOT NULL REFERENCES cars(id),
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS llm_messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
token_id INTEGER NOT NULL REFERENCES diagnostic_tokens(id),
role TEXT NOT NULL, -- 'system' | 'user' | 'assistant'
content TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS ecu_parameters (
id INTEGER PRIMARY KEY AUTOINCREMENT,
token_id INTEGER NOT NULL REFERENCES diagnostic_tokens(id),
pid_code TEXT NOT NULL, -- напр. '0105', '010C'
name TEXT, -- напр. 'coolant_temp', 'rpm'
value REAL,
unit TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS dtc_codes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
token_id INTEGER NOT NULL REFERENCES diagnostic_tokens(id),
code TEXT NOT NULL, -- напр. 'P0301'
description TEXT,
status TEXT, -- 'stored' | 'pending'
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS sessions ( CREATE TABLE IF NOT EXISTS sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -258,87 +218,3 @@ class Database:
)) ))
self.conn.commit() self.conn.commit()
# ── cars ──────────────────────────────────────────────
def get_or_create_car(self, vin: str) -> int:
"""Возвращает car_id по VIN, создаёт запись если нет."""
row = self.conn.execute("SELECT id FROM cars WHERE vin = ?", (vin,)).fetchone()
if row:
return row["id"]
cur = self.conn.execute("INSERT INTO cars (vin) VALUES (?)", (vin,))
self.conn.commit()
return cur.lastrowid
def update_car_info(self, car_id: int, make: str, model: str, year: int, engine: str):
self.conn.execute(
"UPDATE cars SET make=?, model=?, year=?, engine=? WHERE id=?",
(make, model, year, engine, car_id),
)
self.conn.commit()
# ── tokens ────────────────────────────────────────────
def create_token(self, car_id: int) -> int:
"""Создаёт новую сессию диагностики, возвращает token_id."""
cur = self.conn.execute(
"INSERT INTO diagnostic_tokens (car_id) VALUES (?)", (car_id,)
)
self.conn.commit()
return cur.lastrowid
def last_token_for_car(self, car_id: int) -> int | None:
"""Последняя сессия для VIN (для продолжения диалога), или None."""
row = self.conn.execute(
"SELECT id FROM diagnostic_tokens WHERE car_id=? ORDER BY created_at DESC LIMIT 1",
(car_id,),
).fetchone()
return row["id"] if row else None
# ── llm_messages ──────────────────────────────────────
def add_llm_message(self, token_id: int, role: str, content: str):
self.conn.execute(
"INSERT INTO llm_messages (token_id, role, content) VALUES (?, ?, ?)",
(token_id, role, content),
)
self.conn.commit()
def get_llm_messages(self, token_id: int) -> list[dict]:
"""Возвращает историю диалога для токена."""
rows = self.conn.execute(
"SELECT role, content FROM llm_messages WHERE token_id=? ORDER BY id",
(token_id,),
).fetchall()
return [{"role": r["role"], "content": r["content"]} for r in rows]
# ── ecu_parameters ────────────────────────────────────
def add_parameter(self, token_id: int, pid_code: str, name: str, value: float, unit: str):
self.conn.execute(
"INSERT INTO ecu_parameters (token_id, pid_code, name, value, unit) VALUES (?, ?, ?, ?, ?)",
(token_id, pid_code, name, value, unit),
)
self.conn.commit()
def get_parameters(self, token_id: int) -> list[dict]:
rows = self.conn.execute(
"SELECT pid_code, name, value, unit FROM ecu_parameters WHERE token_id=? ORDER BY id",
(token_id,),
).fetchall()
return [dict(r) for r in rows]
# ── dtc_codes ─────────────────────────────────────────
def add_dtc(self, token_id: int, code: str, description: str = "", status: str = "stored"):
self.conn.execute(
"INSERT INTO dtc_codes (token_id, code, description, status) VALUES (?, ?, ?, ?)",
(token_id, code, description, status),
)
self.conn.commit()
def get_dtcs(self, token_id: int) -> list[dict]:
rows = self.conn.execute(
"SELECT code, description, status FROM dtc_codes WHERE token_id=? ORDER BY id",
(token_id,),
).fetchall()
return [dict(r) for r in rows]
+90
View File
@@ -0,0 +1,90 @@
"""Эндпоинты 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]:
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 из справочника."""
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():
"""Приём кодов ошибок (без полной диагностики)."""
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)
+64
View File
@@ -0,0 +1,64 @@
"""Эндпоинты проверки доступности.
GET /api/v1/ping — проверка сервера
GET /api/v1/ping-llm — проверка LLM (с адаптивным кэшем)
"""
import logging
import time
from flask import jsonify, request
from api.config import load
from brain.client import Diagnoser
logger = logging.getLogger("elmer.ping")
# Кэш для /ping-llm (успех=60с, ошибка=7с)
_ping_llm_cache: dict = {}
def register(app):
"""Регистрирует ping-эндпоинты на Flask-приложении."""
@app.route("/api/v1/ping", methods=["GET"])
def ping():
"""Быстрая проверка доступности сервера."""
return {"ok": True}
@app.route("/api/v1/ping-llm", methods=["GET"])
def ping_llm():
"""Проверка LLM с адаптивным кэшем.
- Успех → кэш 60с
- Ошибка → кэш 7с (LLM мог уже ожить)
"""
global _ping_llm_cache
now = time.time()
if _ping_llm_cache:
ttl = _ping_llm_cache.get("ttl", 7)
if (now - _ping_llm_cache.get("ts", 0)) < ttl:
return jsonify(_ping_llm_cache["data"])
cfg = load()
api_key = cfg["llm"]["api_key"]
if not api_key:
result = {"ok": False, "error": "no API key"}
else:
t0 = time.time()
try:
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"),
)
diagnoser.diagnose("Отвечай одним словом.", "OK")
ms = int((time.time() - t0) * 1000)
result = {"ok": True, "ms": ms}
except Exception as e:
ms = int((time.time() - t0) * 1000)
result = {"ok": False, "ms": ms, "error": "LLM unavailable"}
ttl = 60 if result.get("ok") else 7
_ping_llm_cache = {"ts": now, "data": result, "ttl": ttl}
return jsonify(result)
+5 -119
View File
@@ -1,12 +1,14 @@
"""Эндпоинты для толстого клиента: скрипты и батчевая загрузка. """Эндпоинты: скрипт, загрузка сессии, чат.
GET /api/v1/script — выдача скрипта диагностики GET /api/v1/script — выдача скрипта диагностики
POST /api/v1/session/upload — приём батча, LLM-анализ, возврат диагноза POST /api/v1/session/upload — приём батча + LLM
POST /api/v1/chat — свободный вопрос к LLM
См. также: api/dtc.py (DTC), api/ping.py (ping)
""" """
import logging import logging
import time import time
from pathlib import Path
from flask import jsonify, request from flask import jsonify, request
@@ -19,9 +21,6 @@ from brain.prompts import SYSTEM_PROMPT
logger = logging.getLogger("elmer.script") logger = logging.getLogger("elmer.script")
# Кэш для /ping-llm (60 секунд)
_ping_llm_cache: dict = {}
def _build_diagnosis_prompt(data: dict, car_info: str = "") -> str: def _build_diagnosis_prompt(data: dict, car_info: str = "") -> str:
"""Строит промпт для LLM из распарсенных данных.""" """Строит промпт для LLM из распарсенных данных."""
@@ -65,20 +64,6 @@ def _build_diagnosis_prompt(data: dict, car_info: str = "") -> str:
# Загрузка справочника DTC # Загрузка справочника DTC
_DTC_CODES: dict[str, str] = {} _DTC_CODES: dict[str, str] = {}
def _load_dtc_dict():
global _DTC_CODES
if _DTC_CODES:
return _DTC_CODES
path = Path(__file__).parent.parent / "doc" / "dtc_codes.txt"
if path.exists():
for line in 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): def register(app):
"""Регистрирует эндпоинты скриптов на Flask-приложении.""" """Регистрирует эндпоинты скриптов на Flask-приложении."""
@@ -209,105 +194,6 @@ def register(app):
return jsonify({"answer": answer}) return jsonify({"answer": answer})
@app.route("/api/v1/ping", methods=["GET"])
def ping():
"""Быстрая проверка доступности сервера."""
return {"ok": True}
@app.route("/api/v1/ping-llm", methods=["GET"])
def ping_llm():
"""Проверка доступности LLM с адаптивным кэшем.
- Успех → кэш 60с (нет смысла переспрашивать)
- Ошибка → кэш 7с (возможно LLM уже ожил)
"""
global _ping_llm_cache
now = time.time()
if _ping_llm_cache:
ttl = _ping_llm_cache.get("ttl", 7)
if (now - _ping_llm_cache.get("ts", 0)) < ttl:
return jsonify(_ping_llm_cache["data"])
cfg = load()
api_key = cfg["llm"]["api_key"]
if not api_key:
result = {"ok": False, "error": "no API key"}
else:
t0 = time.time()
try:
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"),
)
diagnoser.diagnose("Отвечай одним словом.", "OK")
ms = int((time.time() - t0) * 1000)
result = {"ok": True, "ms": ms}
except Exception as e:
ms = int((time.time() - t0) * 1000)
result = {"ok": False, "ms": ms, "error": "LLM unavailable"}
ttl = 60 if result.get("ok") else 7
_ping_llm_cache = {"ts": now, "data": result, "ttl": ttl}
return jsonify(result)
@app.route("/api/v1/dtc/decode", methods=["POST"])
def dtc_decode():
"""Расшифровка кодов DTC."""
data = request.get_json(silent=True) or {}
codes = data.get("codes", [])
if not codes:
return jsonify({"codes": []})
dtc = _load_dtc_dict()
decoded = []
for c in codes:
c = c.strip().upper()
decoded.append({"code": c, "desc": dtc.get(c, c)})
return jsonify({"codes": decoded})
@app.route("/api/v1/dtc/upload", methods=["POST"])
def dtc_upload():
"""Приём кодов ошибок без полной диагностики."""
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)
def _summary(p: dict) -> dict: def _summary(p: dict) -> dict:
return { return {
+3 -103
View File
@@ -34,114 +34,14 @@ obd/protocol.py — ELM327 стейт-машина (AndrOBD).
import logging import logging
import time import time
from enum import Enum, auto
from typing import Optional from typing import Optional
from obd.state import State, Rsp
from obd.timing import AdaptiveTiming
logger = logging.getLogger("androbd") logger = logging.getLogger("androbd")
class State(Enum):
"""Состояния стейт-машины (AndrOBD STAT).
UNDEFINED → INITIALIZING → READY — нормальный путь.
BUSY — во время выполнения команды.
ERROR/DISCONNECTED — ошибка, требуется восстановление.
"""
UNDEFINED = auto()
INITIALIZING = auto()
READY = auto()
BUSY = auto()
ERROR = auto()
DISCONNECTED = auto()
class Rsp:
"""Классификация ответов ELM327 (AndrOBD RSP_ID).
Каждый сырой ответ классифицируется:
- PROMPT (`>`) — готов к следующей команде
- OK — команда выполнена
- SEARCHING — идёт поиск протокола
- BUS_ERROR/BUS_BUSY/CAN_ERROR/STOPPED — ошибка шины → DISCONNECTED
- ERROR/DATA_ERROR/BUFFER_FULL — ошибка → warm start
- UNKNOWN — данные (ответ на PID/DTC)
"""
PROMPT = ">"
OK = "OK"
SEARCHING = "SEARCHING"
NODATA = "NODATA"
ERROR = "ERROR"
UNABLE = "UNABLE"
BUS_BUSY = "BUS BUSY"
BUS_ERROR = "BUS ERROR"
CAN_ERROR = "CAN ERROR"
BUS_INIT = "BUS INIT"
STOPPED = "STOPPED"
DATA_ERROR = "DATA ERROR"
BUFFER_FULL= "BUFFER FULL"
RX_ERROR = "RX ERROR"
UNKNOWN = ""
@classmethod
def identify(cls, raw: str) -> str:
"""Определяет тип ответа по сырой строке."""
u = raw.upper().strip()
for tag in (cls.SEARCHING, cls.NODATA, cls.ERROR, cls.UNABLE,
cls.BUS_BUSY, cls.BUS_ERROR, cls.CAN_ERROR,
cls.BUS_INIT, cls.STOPPED, cls.DATA_ERROR,
cls.BUFFER_FULL, cls.RX_ERROR, cls.OK):
if u.startswith(tag):
return tag
if raw.strip() == ">":
return cls.PROMPT
return cls.UNKNOWN
class AdaptiveTiming:
"""Адаптивный таймаут ожидания ответа (AndrOBD AdaptiveTiming).
Динамически подстраивается под скорость ответа ЭБУ:
- Успешный ответ → уменьшаем таймаут (быстрее)
- Таймаут/NO DATA → увеличиваем таймаут (медленнее)
- BUS ERROR → сброс до DEFAULT
ATST = таймаут / 4 (отправляется в ELM327 как ATSTxx).
"""
DEFAULT = 500 # мс — начальный таймаут
MIN = 50 # мс — минимальный
MAX = 2000 # мс — максимальный
STEP = 20 # мс — шаг изменения
RES = 4 # делитель для ATST
def __init__(self):
self._t = self.DEFAULT
self._min = self.MIN
@property
def ms(self) -> int:
"""Текущий таймаут в миллисекундах."""
return self._t
@property
def atst(self) -> int:
"""Значение для ATST (таймаут / 4)."""
return max(1, self._t // self.RES)
def increase(self):
"""Увеличить таймаут (ЭБУ медленно отвечает)."""
if self._t + self.STEP < self.MAX:
self._t += self.STEP
def decrease(self):
"""Уменьшить таймаут (ЭБУ отвечает быстро)."""
if self._t - self.STEP >= self._min:
self._t -= self.STEP
def reset(self):
"""Сбросить до DEFAULT (после BUS ERROR)."""
self._t = self.DEFAULT
class AndrOBD: class AndrOBD:
"""Стейт-машина ELM327 — 1:1 копия AndrOBD (ElmProt.java). """Стейт-машина ELM327 — 1:1 копия AndrOBD (ElmProt.java).
+64
View File
@@ -0,0 +1,64 @@
"""Состояния и классификация ответов ELM327 (AndrOBD STAT/RSP_ID).
State — состояния стейт-машины (UNDEFINED → INITIALIZING → READY → BUSY → ...)
Rsp — классификация сырых ответов ELM327
"""
from enum import Enum, auto
class State(Enum):
"""Состояния стейт-машины (AndrOBD STAT).
UNDEFINED → INITIALIZING → READY — нормальный путь.
BUSY — во время выполнения команды.
ERROR/DISCONNECTED — ошибка, требуется восстановление.
"""
UNDEFINED = auto()
INITIALIZING = auto()
READY = auto()
BUSY = auto()
ERROR = auto()
DISCONNECTED = auto()
class Rsp:
"""Классификация ответов ELM327 (AndrOBD RSP_ID).
Каждый сырой ответ классифицируется:
- PROMPT (`>`) — готов к следующей команде
- OK — команда выполнена
- SEARCHING — идёт поиск протокола
- BUS_ERROR/BUS_BUSY/CAN_ERROR/STOPPED — ошибка шины → DISCONNECTED
- ERROR/DATA_ERROR/BUFFER_FULL — ошибка → warm start
- UNKNOWN — данные (ответ на PID/DTC)
"""
PROMPT = ">"
OK = "OK"
SEARCHING = "SEARCHING"
NODATA = "NODATA"
ERROR = "ERROR"
UNABLE = "UNABLE"
BUS_BUSY = "BUS BUSY"
BUS_ERROR = "BUS ERROR"
CAN_ERROR = "CAN ERROR"
BUS_INIT = "BUS INIT"
STOPPED = "STOPPED"
DATA_ERROR = "DATA ERROR"
BUFFER_FULL= "BUFFER FULL"
RX_ERROR = "RX ERROR"
UNKNOWN = ""
@classmethod
def identify(cls, raw: str) -> str:
"""Определяет тип ответа по сырой строке."""
u = raw.upper().strip()
for tag in (cls.SEARCHING, cls.NODATA, cls.ERROR, cls.UNABLE,
cls.BUS_BUSY, cls.BUS_ERROR, cls.CAN_ERROR,
cls.BUS_INIT, cls.STOPPED, cls.DATA_ERROR,
cls.BUFFER_FULL, cls.RX_ERROR, cls.OK):
if u.startswith(tag):
return tag
if raw.strip() == ">":
return cls.PROMPT
return cls.UNKNOWN
+49
View File
@@ -0,0 +1,49 @@
"""Адаптивный таймаут ожидания ответа ЭБУ (AndrOBD AdaptiveTiming).
Динамически подстраивается под скорость ответа:
- Успешный ответ → уменьшаем таймаут
- Таймаут/NO DATA → увеличиваем таймаут
- BUS ERROR → сброс до DEFAULT
ATST = таймаут / 4 (отправляется в ELM327 как ATSTxx).
"""
class AdaptiveTiming:
"""Адаптивный таймаут ожидания ответа (AndrOBD AdaptiveTiming).
ATST = таймаут / 4 (отправляется в ELM327 как ATSTxx).
"""
DEFAULT = 500 # мс — начальный таймаут
MIN = 50 # мс — минимальный
MAX = 2000 # мс — максимальный
STEP = 20 # мс — шаг изменения
RES = 4 # делитель для ATST
def __init__(self):
self._t = self.DEFAULT
self._min = self.MIN
@property
def ms(self) -> int:
"""Текущий таймаут в миллисекундах."""
return self._t
@property
def atst(self) -> int:
"""Значение для ATST (таймаут / 4)."""
return max(1, self._t // self.RES)
def increase(self):
"""Увеличить таймаут (ЭБУ медленно отвечает)."""
if self._t + self.STEP < self.MAX:
self._t += self.STEP
def decrease(self):
"""Уменьшить таймаут (ЭБУ отвечает быстро)."""
if self._t - self.STEP >= self._min:
self._t -= self.STEP
def reset(self):
"""Сбросить до DEFAULT (после BUS ERROR)."""
self._t = self.DEFAULT
+2 -1
View File
@@ -18,7 +18,8 @@ os.environ["ELMER_CONFIG"] = str(Path(__file__).parent / "config.yaml")
from api.db import Database from api.db import Database
from api.parser import parse_batch, format_no_llm from api.parser import parse_batch, format_no_llm
from api.routes import _load_dtc_dict, _build_diagnosis_prompt from api.routes import _build_diagnosis_prompt
from api.dtc import _load_dtc_dict
from api.scripts import build_default_script, build_full_script from api.scripts import build_default_script, build_full_script
passed = 0 passed = 0
+4
View File
@@ -20,12 +20,16 @@ from flask import Flask, jsonify, redirect, render_template, request, send_from_
from api.config import load from api.config import load
from api.routes import register as register_api from api.routes import register as register_api
from api.dtc import register as register_dtc
from api.ping import register as register_ping
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(name)s] %(message)s") logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(name)s] %(message)s")
app = Flask(__name__) app = Flask(__name__)
config = load() config = load()
register_api(app) register_api(app)
register_dtc(app)
register_ping(app)
@app.route("/") @app.route("/")