feat(tagging): title-based WIP auto-tagging (#1458)
Auto-apply the `wip` system tag to posts whose TITLE explicitly declares
work-in-progress ("WIP" / "work in progress") — a deterministic, high-precision
complement to the image-based ML `wip` head. WIP images are excluded from the
Explore/gallery browse, so honouring the artist's own label keeps unfinished
pieces out of the main browse.
- services/wip_title.py: precision-first token-anchored matcher (swipe/wiped
never trip it) + sync apply helpers (source='wip_title', ON CONFLICT DO
NOTHING, chunked under the psycopg param ceiling).
- importer: live hook on FRESH import only (never on deep-scan/supersede), so a
manually-removed WIP tag is never re-applied by a routine re-scan.
- maintenance.backfill_wip_title_tags: operator-triggered back-catalogue sweep
(coarse SQL prefilter + regex confirm, keyset-paginated). Deliberately NOT a
beat — a periodic re-run would silently undo manual removals.
- ImportSettings.wip_title_tagging_enabled (default ON, migration 0085) gating
the live hook; GET/PATCH + POST /settings/wip-title/scan.
- Settings UI: toggle + "Scan existing posts" button.
- Tests: pure matcher unit tests + integration (apply idempotency, backfill
precision).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -48,6 +48,7 @@ _EDITABLE_FIELDS = (
|
||||
"interpreter_base_url",
|
||||
"translation_target_lang",
|
||||
"translation_min_confidence",
|
||||
"wip_title_tagging_enabled",
|
||||
)
|
||||
|
||||
# Per-host external-download toggles — all plain booleans, validated uniformly.
|
||||
@@ -89,6 +90,7 @@ async def get_import_settings():
|
||||
"interpreter_base_url": row.interpreter_base_url,
|
||||
"translation_target_lang": row.translation_target_lang,
|
||||
"translation_min_confidence": row.translation_min_confidence,
|
||||
"wip_title_tagging_enabled": row.wip_title_tagging_enabled,
|
||||
})
|
||||
|
||||
|
||||
@@ -171,6 +173,12 @@ async def update_import_settings():
|
||||
return jsonify(
|
||||
{"error": "series_suggest_threshold must be a number in [0, 1]"}
|
||||
), 400
|
||||
if "wip_title_tagging_enabled" in body and not isinstance(
|
||||
body["wip_title_tagging_enabled"], bool
|
||||
):
|
||||
return jsonify(
|
||||
{"error": "wip_title_tagging_enabled must be a boolean"}
|
||||
), 400
|
||||
|
||||
async with get_session() as session:
|
||||
row = await ImportSettings.load(session)
|
||||
@@ -182,6 +190,18 @@ async def update_import_settings():
|
||||
return await get_import_settings()
|
||||
|
||||
|
||||
@settings_bp.route("/settings/wip-title/scan", methods=["POST"])
|
||||
async def wip_title_scan():
|
||||
"""Enqueue the back-catalogue WIP-title scan (task #1458 Settings button):
|
||||
apply the `wip` system tag to EXISTING posts whose title declares
|
||||
work-in-progress. New imports are tagged live by the importer; this catches
|
||||
the existing library. Returns the Celery task id (202)."""
|
||||
from ..tasks.maintenance import backfill_wip_title_tags
|
||||
|
||||
r = backfill_wip_title_tags.delay()
|
||||
return jsonify({"celery_task_id": r.id}), 202
|
||||
|
||||
|
||||
@settings_bp.route("/system/stats", methods=["GET"])
|
||||
async def system_stats():
|
||||
async with get_session() as session:
|
||||
|
||||
Reference in New Issue
Block a user