diff --git a/History/2026-08-26-full-replace-fix.md b/History/2026-08-26-full-replace-fix.md new file mode 100644 index 0000000..af8687a --- /dev/null +++ b/History/2026-08-26-full-replace-fix.md @@ -0,0 +1,34 @@ +# full_replace: фикс дублирования строк спецификации (v2.0.16) + +## Проблема + +`mode="full_replace"` (LLM возвращает полную новую редакцию — все ADD) +применялся поверх существующей `spec_current` без очистки. + +`reset(contract_id)` срабатывает только один раз на старте пайплайна, а +full_replace происходит ПОСЛЕ — строки новой редакции плюсовались к старым → +дубли в `spec_current`. + +## Фикс + +1. `site/db/spec_events.py` — добавлена `clear_current(contract_id)`: + очищает ТОЛЬКО `spec_current` (история `spec_events` сохраняется). + +2. `site/services/process.py` — перед `apply_ops()`: + ```python + if mode == "full_replace": + spec_events.clear_current(contract_id) + ``` + +## Почему не `reset()` + +`reset()` чистит и `spec_events`, и `spec_current` — потеряли бы историю +операций текущей редакции. Для full_replace нужна только очистка текущего +состояния, журнал ADDs должен остаться. + +## Сопутствующее + +- Скорректировано архитектурное описание → `/home/naeel/nubes/contracts/DOC/architecture-contracts-flask.md`. + Исправлена неточность: ZIP раскрывается **на клиенте** (`expandZipClient` → + `window.listZipFiles`), а не серверным `/unzip-upload` (legacy). +- Версия: 2.0.15 → 2.0.16. diff --git a/site/config.py b/site/config.py index 3601948..be91b62 100644 --- a/site/config.py +++ b/site/config.py @@ -1,7 +1,7 @@ """Конфигурация приложения — все настройки в одном месте.""" import os -VERSION = "2.0.15" +VERSION = "2.0.16" LLM_URL = os.getenv("LLM_API_URL", "https://api.aillm.ru/v1/chat/completions") LLM_KEY = os.getenv("LLM_API_KEY", "") diff --git a/site/db/spec_events.py b/site/db/spec_events.py index 2ea4f58..1a82fbe 100644 --- a/site/db/spec_events.py +++ b/site/db/spec_events.py @@ -9,6 +9,11 @@ def reset(contract_id): execute("DELETE FROM spec_events WHERE contract_id = %s", (contract_id,)) +def clear_current(contract_id): + """Очистить ТОЛЬКО текущее состояние спецификации (история spec_events сохраняется). Для full_replace.""" + execute("DELETE FROM spec_current WHERE contract_id = %s", (contract_id,)) + + def get_next_seq(contract_id): """Get next sequence number. WAL serializes writers — no explicit lock needed.""" conn = get_conn() diff --git a/site/services/process.py b/site/services/process.py index ea4a646..760d0ad 100644 --- a/site/services/process.py +++ b/site/services/process.py @@ -98,6 +98,11 @@ def run_pipeline(contract_id, order_ids, build_prompt_fn): "time_s": round(time.time() - t1, 1), } + # full_replace: очистить текущее состояние, чтобы ADD-строки новой редакции + # не дублировали старые (reset на старте пайплайна это не покрывает). + if mode == "full_replace": + spec_events.clear_current(contract_id) + # Apply ops to DB via apply_ops try: summary = spec_events.apply_ops(