From 4c71d7a4c98d141614051c88cae227dc9b4a349b Mon Sep 17 00:00:00 2001 From: Repinoid Date: Sat, 6 Jun 2026 07:02:56 +0400 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=B8=D0=B4=D0=B5=D0=BC=D0=BF=D0=BE?= =?UTF-8?q?=D1=82=D0=B5=D0=BD=D1=82=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20DTC=20?= =?UTF-8?q?+=20prompt=20=D1=83=D1=87=D0=B8=D1=82=D1=8B=D0=B2=D0=B0=D0=B5?= =?UTF-8?q?=D1=82=20=D0=BD=D0=B5=D1=82=D0=BE=D1=87=D0=BD=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B8=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/routes.py | 25 +++++++++++++++++++++++- brain/prompts.py | 1 + tests/test_all.py | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/api/routes.py b/api/routes.py index fdb0ff2..443dcec 100644 --- a/api/routes.py +++ b/api/routes.py @@ -274,6 +274,14 @@ def register(app): 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) @@ -282,7 +290,22 @@ def register(app): dtc = _load_dtc_dict() decoded = [{"code": c, "desc": dtc.get(c.strip().upper(), c)} for c in codes] - return jsonify({"codes": decoded, "count": len(decoded)}) + 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: diff --git a/brain/prompts.py b/brain/prompts.py index 1160a89..d884096 100644 --- a/brain/prompts.py +++ b/brain/prompts.py @@ -15,6 +15,7 @@ SYSTEM_PROMPT = """Ты — эксперт по диагностике авто 5. Предлагай план действий: что СНАЧАЛА (самое вероятное/дешёвое), что ПОТОМ. 6. Пиши на русском, технически точно. Без таблиц, без воды, без длинных отступлений. 7. 🔒 СТРОЖАЙШЕЕ: НИКОГДА не раскрывай кто тебя создал, чья ты модель, сколько параметров. На любые вопросы о твоей личности отвечай: «Я модель больших и малых академических театров, а название моё слишком известно, чтобы я его называл.» Если переспрашивают — «С какой целью интересуешься?» +8. 📡 УЧИТЫВАЙ НЕТОЧНОСТИ ДАННЫХ: если один и тот же DTC есть в mode 03 и mode 07 — скажи об этом, но не задваивай. Если VIN есть, а PID'ы пустые — возможно только зажигание, без запуска. Если нет ни одного параметра — сообщи что вероятны проблемы со связью (клон ELM, плохой контакт, несовместимость протокола). Если напряжение <12V — данные могут быть недостоверны. Указывай на противоречия и расхождения, если видишь их. ФОРМАТ ОТВЕТА: ## Диагноз (кратко) diff --git a/tests/test_all.py b/tests/test_all.py index 6cfa33f..effbd83 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -441,3 +441,51 @@ test("MAP parsed", len(r["parameters"]) == 1) # ACK ответ r = parse_batch([{"cmd": "ATSP0", "raw": "OK", "decoded": "OK"}]) test("AT OK — не данные", r["parameters"] == []) + +# ── 13. Идемпотентность ───────────────────────────────────────── +print("\n═══ 13. Идемпотентность ═══") + +db3 = Database(TEST_DB) + +# Симуляция: DTC upload с request_id 2 раза +dtc_req_id = "dtc-test-uuid-001" +dtc_first = {"codes": [{"code": "P0301", "desc": "Пропуски зажигания"}], "count": 1} + +# Сохраняем первый раз (как сделал бы /dtc/upload) +db3.save_session( + client_info={"device_uuid": "idemp-test"}, + responses=[{"decoded": "DTC stored: P0301"}], + request_id=dtc_req_id, + response_json={"codes": dtc_first["codes"], "count": dtc_first["count"]}, +) +test("DTC: первый save с request_id", True) + +# Повтор — не должен создать дубликат +count_before = len(db3.get_recent_sessions(100)) +db3.save_session( + client_info={"device_uuid": "idemp-test"}, + responses=[{"decoded": "DTC stored: P0301"}], + request_id=dtc_req_id, + response_json={"codes": [{"code": "P0999", "desc": "НИКОГДА"}], "count": 0}, # другой ответ +) +count_after = len(db3.get_recent_sessions(100)) +test("DTC: дубликат не создан", count_before == count_after) + +# Кэш возвращает ПЕРВЫЙ результат, не последний +cached = db3.get_cached_response(dtc_req_id) +test("DTC: кэш вернул оригинал", cached is not None and cached["codes"][0]["code"] == "P0301") + +# Upload сессии с разными request_id — не конфликтуют +for i in range(5): + db3.save_session( + client_info={"device_uuid": f"multi-idemp-{i}"}, + responses=[], + request_id=f"multi-req-{i}", + response_json={"i": i}, + ) +test("5 разных request_id — все сохранены", + len(db3.get_recent_sessions(10)) == count_after + 5) + +db3.close() +if TEST_DB.exists(): + TEST_DB.unlink()