elmAI rebrand: визуальные названия Elmer → elmAI
This commit is contained in:
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
llm:
|
||||
api_key: "sk-ucI5YvOticoOQ9Kuj5K9mQ"
|
||||
model: "gpt-oss-120b"
|
||||
model: "qwen3-6-27b-fp8"
|
||||
base_url: "https://api.aillm.ru/v1"
|
||||
|
||||
elm327:
|
||||
|
||||
+122
@@ -6,8 +6,10 @@
|
||||
llm_messages — token_id (FK), role, content, timestamp
|
||||
ecu_parameters — token_id (FK), pid_code, value, unit, timestamp
|
||||
dtc_codes — token_id (FK), code, description, status
|
||||
sessions — сводная таблица всех сессий (клиент, ELM, авто, LLM)
|
||||
"""
|
||||
|
||||
import json
|
||||
import sqlite3
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
@@ -64,9 +66,129 @@ class Database:
|
||||
status TEXT, -- 'stored' | 'pending'
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
-- Сервер
|
||||
client_ip TEXT,
|
||||
real_ip TEXT,
|
||||
user_agent TEXT,
|
||||
content_length INTEGER,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
|
||||
-- Телефон
|
||||
phone_model TEXT,
|
||||
phone_maker TEXT,
|
||||
android_version TEXT,
|
||||
android_sdk INTEGER,
|
||||
app_version TEXT,
|
||||
android_id TEXT,
|
||||
|
||||
-- ELM327
|
||||
elm_mac TEXT,
|
||||
elm_bt_name TEXT,
|
||||
obd_protocol TEXT,
|
||||
|
||||
-- Авто
|
||||
vin TEXT,
|
||||
dtc_count INTEGER DEFAULT 0,
|
||||
pid_count INTEGER DEFAULT 0,
|
||||
|
||||
-- Сессия
|
||||
duration_ms INTEGER,
|
||||
response_count INTEGER DEFAULT 0,
|
||||
error_count INTEGER DEFAULT 0,
|
||||
retry_count INTEGER DEFAULT 0,
|
||||
timeout_count INTEGER DEFAULT 0,
|
||||
script_mode TEXT,
|
||||
transport TEXT, -- 'bt' | 'tcp'
|
||||
mock_mode INTEGER DEFAULT 0,
|
||||
|
||||
-- LLM
|
||||
diagnosis_text TEXT,
|
||||
diagnosis_len INTEGER,
|
||||
llm_model TEXT,
|
||||
llm_duration_ms INTEGER,
|
||||
llm_success INTEGER DEFAULT 0,
|
||||
|
||||
-- Сырые данные (JSON)
|
||||
raw_responses TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_created ON sessions(created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_vin ON sessions(vin);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_mac ON sessions(elm_mac);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_aid ON sessions(android_id);
|
||||
""")
|
||||
self.conn.commit()
|
||||
|
||||
# ── sessions ──────────────────────────────────────────
|
||||
|
||||
def save_session(self, client_info: dict, responses: list[dict],
|
||||
diagnosis: str = "", llm_model: str = "",
|
||||
llm_duration_ms: int = 0, llm_success: bool = False):
|
||||
"""Сохраняет сводную запись о сессии."""
|
||||
ci = client_info
|
||||
|
||||
# Подсчёт DTC/PID из ответов
|
||||
dtc_count = 0
|
||||
pid_count = 0
|
||||
for r in responses:
|
||||
dec = (r.get("decoded") or "").lower()
|
||||
if dec.startswith("dtc"):
|
||||
dtc_count += 1
|
||||
elif ":" in dec and not dec.startswith(("vin", "dtc", "elm", "protocol")):
|
||||
pid_count += 1
|
||||
|
||||
# VIN из ответов
|
||||
vin = None
|
||||
for r in responses:
|
||||
dec = (r.get("decoded") or "")
|
||||
if dec.startswith("VIN:"):
|
||||
vin = dec[4:].strip()
|
||||
if len(vin) != 17:
|
||||
vin = None
|
||||
break
|
||||
|
||||
self.conn.execute("""
|
||||
INSERT INTO sessions (
|
||||
client_ip, real_ip, user_agent, content_length,
|
||||
phone_model, phone_maker, android_version, android_sdk,
|
||||
app_version, android_id,
|
||||
elm_mac, elm_bt_name, obd_protocol,
|
||||
vin, dtc_count, pid_count,
|
||||
duration_ms, response_count, error_count,
|
||||
retry_count, timeout_count, script_mode,
|
||||
transport, mock_mode,
|
||||
diagnosis_text, diagnosis_len, llm_model,
|
||||
llm_duration_ms, llm_success,
|
||||
raw_responses
|
||||
) VALUES (?,?,?,?, ?,?,?,?, ?,?, ?,?,?, ?,?,?, ?,?,?, ?,?,?, ?,?,
|
||||
?,?,?, ?,?, ?)
|
||||
""", (
|
||||
ci.get("client_ip"), ci.get("real_ip"), ci.get("user_agent"),
|
||||
ci.get("content_length"),
|
||||
ci.get("phone_model"), ci.get("phone_maker"), ci.get("android_version"),
|
||||
ci.get("android_sdk"), ci.get("app_version"), ci.get("android_id"),
|
||||
ci.get("elm_mac"), ci.get("elm_bt_name"), ci.get("obd_protocol"),
|
||||
vin, dtc_count, pid_count,
|
||||
ci.get("duration_ms"), len(responses), ci.get("error_count", 0),
|
||||
ci.get("retry_count", 0), ci.get("timeout_count", 0),
|
||||
ci.get("script_mode"), ci.get("transport"), ci.get("mock_mode", 0),
|
||||
diagnosis, len(diagnosis), llm_model,
|
||||
llm_duration_ms, 1 if llm_success else 0,
|
||||
json.dumps(responses, ensure_ascii=False) if responses else None,
|
||||
))
|
||||
self.conn.commit()
|
||||
|
||||
def get_recent_sessions(self, limit: int = 50) -> list[dict]:
|
||||
"""Последние N сессий."""
|
||||
rows = self.conn.execute(
|
||||
"SELECT * FROM sessions ORDER BY created_at DESC LIMIT ?", (limit,)
|
||||
).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
# ── cars ──────────────────────────────────────────────
|
||||
|
||||
def get_or_create_car(self, vin: str) -> int:
|
||||
|
||||
+42
-13
@@ -5,6 +5,7 @@ POST /api/v1/session/upload — приём батча, LLM-анализ, воз
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
from web.script_builder import build_default_script, build_full_script
|
||||
from web.script_parser import parse_batch, format_no_llm
|
||||
|
||||
@@ -63,6 +64,7 @@ def register(app):
|
||||
def upload_session():
|
||||
from flask import request, jsonify
|
||||
from elmer.config import load
|
||||
from elmer.db import Database
|
||||
from elmer.diagnose import Diagnoser
|
||||
from elmer.prompts import SYSTEM_PROMPT
|
||||
|
||||
@@ -72,30 +74,57 @@ def register(app):
|
||||
|
||||
responses = data["responses"]
|
||||
logger.info(f"Upload: {len(responses)} responses")
|
||||
|
||||
# ── Информация о клиенте ──────────────────────
|
||||
client_info = data.get("client_info", {})
|
||||
client_info["client_ip"] = request.remote_addr
|
||||
client_info["real_ip"] = request.headers.get("X-Real-IP", "")
|
||||
client_info["user_agent"] = request.headers.get("User-Agent", "")
|
||||
client_info["content_length"] = request.content_length
|
||||
|
||||
parsed = parse_batch(responses)
|
||||
|
||||
cfg = load()
|
||||
api_key = cfg["llm"]["api_key"]
|
||||
model = cfg["llm"].get("model", "gpt-oss-120b")
|
||||
|
||||
llm_start = time.time()
|
||||
llm_success = False
|
||||
diagnosis = ""
|
||||
|
||||
if not api_key:
|
||||
return jsonify({
|
||||
"diagnosis": format_no_llm(parsed),
|
||||
"parsed": _summary(parsed),
|
||||
})
|
||||
diagnosis = format_no_llm(parsed)
|
||||
else:
|
||||
diagnoser = Diagnoser(
|
||||
api_key=api_key,
|
||||
model=model,
|
||||
base_url=cfg["llm"].get("base_url", "https://api.aillm.ru/v1"),
|
||||
)
|
||||
try:
|
||||
diagnosis = diagnoser.diagnose(SYSTEM_PROMPT, _build_diagnosis_prompt(parsed))
|
||||
llm_success = True
|
||||
except Exception as e:
|
||||
logger.warning(f"LLM failed: {e}")
|
||||
diagnosis = format_no_llm(parsed) + f"\n\n(LLM недоступен: {e})"
|
||||
|
||||
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"),
|
||||
)
|
||||
llm_duration_ms = int((time.time() - llm_start) * 1000)
|
||||
|
||||
# ── Сохранение в БД ───────────────────────────
|
||||
try:
|
||||
answer = diagnoser.diagnose(SYSTEM_PROMPT, _build_diagnosis_prompt(parsed))
|
||||
db = Database()
|
||||
db.save_session(
|
||||
client_info=client_info,
|
||||
responses=responses,
|
||||
diagnosis=diagnosis,
|
||||
llm_model=model,
|
||||
llm_duration_ms=llm_duration_ms,
|
||||
llm_success=llm_success,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"LLM failed: {e}")
|
||||
answer = format_no_llm(parsed) + f"\n\n(LLM недоступен: {e})"
|
||||
logger.error(f"DB save failed: {e}")
|
||||
|
||||
return jsonify({
|
||||
"diagnosis": answer,
|
||||
"diagnosis": diagnosis,
|
||||
"parsed": _summary(parsed),
|
||||
})
|
||||
|
||||
|
||||
@@ -4,22 +4,22 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Elmer — Диагностика авто</title>
|
||||
<title>elmAI — Диагностика авто</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🔧 Elmer</h1>
|
||||
<h1>🔧 elmAI</h1>
|
||||
<p class="subtitle">Диагностика авто через ELM327 + ИИ</p>
|
||||
<p class="subtitle" style="font-size:12px;opacity:0.7;">v0.16.0-dev — 28 мая 2026</p>
|
||||
<p class="subtitle" style="font-size:12px;opacity:0.7;">v0.18.0-dev — 28 мая 2026</p>
|
||||
|
||||
<div class="card" style="text-align:center;margin-bottom:20px;">
|
||||
<p style="margin:0 0 10px 0;">📱 Скачай приложение на телефон:</p>
|
||||
<a href="/static/app-debug.apk" style="color:#ff6b35;font-size:18px;font-weight:bold;text-decoration:none;">
|
||||
⬇️ Скачать Elmer APK
|
||||
⬇️ Скачать elmAI APK
|
||||
</a>
|
||||
<p style="font-size:11px;opacity:0.6;margin:4px 0 0 0;">v0.16.0-dev • нажмите чтобы скачать</p>
|
||||
<p style="font-size:11px;opacity:0.6;margin:4px 0 0 0;">v0.18.0-dev • нажмите чтобы скачать</p>
|
||||
</div>
|
||||
|
||||
<!-- Кнопка десктоп-диагностики скрыта — только для разработчика с прямым ELM327 -->
|
||||
|
||||
Reference in New Issue
Block a user