From 89928511ba244afc16bb4581e39f0522b284c7dc Mon Sep 17 00:00:00 2001 From: dmitriylaukhin Date: Sat, 27 Jun 2026 10:58:10 +0500 Subject: [PATCH] =?UTF-8?q?feat(=D0=B2=D0=B5=D1=80=D0=B8=D1=84=D0=B8=D0=BA?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F):=20=D1=80=D0=B0=D0=B1=D0=BE=D1=87?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=BE=D1=87=D0=B5=D1=80=D0=B5=D0=B4=D1=8C=20?= =?UTF-8?q?=D1=81=20=D0=BF=D0=B0=D0=B3=D0=B8=D0=BD=D0=B0=D1=86=D0=B8=D0=B5?= =?UTF-8?q?=D0=B9=20+=20/skip?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - автодогрузка порций + кнопка «Загрузить ещё»; разбор сверху по уверенности - POST /skip фиксирует пропуск (таблица skipped_item), очередь его исключает Co-Authored-By: Claude Opus 4.8 (1M context) --- api/main.py | 15 ++++++++++++++- web/index.html | 18 +++++++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/api/main.py b/api/main.py index 06b6264..a72e895 100644 --- a/api/main.py +++ b/api/main.py @@ -36,6 +36,8 @@ app.add_middleware( def db() -> sqlite3.Connection: conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row + # Отложенные оператором позиции (чтобы пропущенное не возвращалось в начало очереди). + conn.execute("CREATE TABLE IF NOT EXISTS skipped_item (item_id INTEGER PRIMARY KEY)") return conn @@ -155,6 +157,7 @@ def unmatched(limit: int = 50, offset: int = 0): JOIN partner p ON pi.partner_id = p.partner_id LEFT JOIN service s ON pi.suggested_service_id = s.service_id WHERE pi.service_id IS NULL AND pi.is_active = 1 + AND pi.item_id NOT IN (SELECT item_id FROM skipped_item) ORDER BY pi.suggested_score DESC, pi.item_id LIMIT ? OFFSET ?""" with db() as conn: @@ -184,12 +187,22 @@ def unmatched(limit: int = 50, offset: int = 0): def unmatched_count(): with db() as conn: n = conn.execute( - "SELECT COUNT(*) FROM price_item WHERE service_id IS NULL AND is_active = 1" + """SELECT COUNT(*) FROM price_item + WHERE service_id IS NULL AND is_active = 1 + AND item_id NOT IN (SELECT item_id FROM skipped_item)""" ).fetchone()[0] learned = conn.execute("SELECT COUNT(*) FROM learned_synonym").fetchone()[0] return {"remaining": n, "learned_synonyms": learned} +@app.post("/skip", summary="Отложить позицию (пропустить в очереди верификации)") +def skip_item(item_id: int): + with db() as conn: + conn.execute("INSERT OR IGNORE INTO skipped_item (item_id) VALUES (?)", [item_id]) + conn.commit() + return {"item_id": item_id, "status": "skipped"} + + @app.post("/match", summary="Ручное сопоставление позиции (с дообучением)") def manual_match(item_id: int, service_id: str): with db() as conn: diff --git a/web/index.html b/web/index.html index ed0346a..4e444f7 100644 --- a/web/index.html +++ b/web/index.html @@ -57,6 +57,7 @@

Система предлагает кандидата из справочника — подтвердите, выберите другой или пропустите. Подтверждение обучает систему.

+ @@ -145,11 +146,17 @@ async function loadAdmin() { } async function loadQueue() { - const items = await fetch(`${API}/unmatched?limit=40`).then(r => r.json()); + const items = await fetch(`${API}/unmatched?limit=25`).then(r => r.json()); $('queue').innerHTML = items.map(renderCard).join('') || '
Очередь пуста 🎉
'; + $('loadmore').classList.toggle('hidden', items.length === 0); loadCounts(); } +function maybeRefill() { + // Рабочая очередь: когда необработанных карточек мало — подгружаем следующую порцию. + if ($('queue').querySelectorAll('[id^="card-"]').length < 6) loadQueue(); +} + async function loadCounts() { const c = await fetch(`${API}/unmatched/count`).then(r => r.json()); $('vprogress').innerHTML = `осталось ${c.remaining} · обучено синонимов ${c.learned_synonyms}`; @@ -189,11 +196,16 @@ async function doMatch(itemId, serviceId, btn) { if (btn) btn.disabled = true; await fetch(`${API}/match?item_id=${itemId}&service_id=${encodeURIComponent(serviceId)}`, { method: 'POST' }); const card = $(`card-${itemId}`); if (card) card.remove(); - loadCounts(); + loadCounts(); maybeRefill(); } function togglePick(id) { $(`pick-${id}`).classList.toggle('hidden'); } -function skipCard(id) { const c = $(`card-${id}`); if (c) c.remove(); } + +async function skipCard(id) { + await fetch(`${API}/skip?item_id=${id}`, { method: 'POST' }); + const c = $(`card-${id}`); if (c) c.remove(); + loadCounts(); maybeRefill(); +} let dictTimer; function searchDict(itemId, q) {