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()