feat(ml): presentation auto-hide settings + review table (#141 step 3)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 20s
CI / backend-lint-and-test (push) Successful in 38s
CI / integration (push) Successful in 3m47s

MLSettings gains presentation_auto_apply_enabled / _threshold (default 0.90) +
presentation_conflict_threshold (default 0.50): banner/editor auto-hide with a
FLAT threshold (decoupled from content-head graduation), plus the "also looks
like content" conflict cut. New presentation_review table (image, presentation
tag, conflict tag + score, created/resolved_at) records auto-hides flagged for
review. Migration 0082 (columns + table), ml_admin API (editable + get_settings
+ _validate bounds), settings roundtrip/bounds test. The sweep that reads these
knobs + the Settings UI land in step 4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
This commit is contained in:
2026-07-06 22:59:00 -04:00
parent eadaa716af
commit ab63d94249
6 changed files with 184 additions and 0 deletions
+16
View File
@@ -84,6 +84,22 @@ class MLSettings(Base):
# character matches auto-tag.
Float, nullable=False, default=0.95
)
# -- Presentation chrome auto-hide (#141) -------------------------------
# banner / editor screenshot auto-apply on the sweep with their OWN flat
# threshold (decoupled from content-head graduation). Hiding is consequential
# so it runs HIGH. `wip` is never auto-applied. When an image would be
# auto-hidden but ALSO scores >= presentation_conflict_threshold on a content
# head, it's still hidden but flagged for review (PresentationReview) instead
# of buried silently. ON by default (opt-out); every auto-tag is reversible.
presentation_auto_apply_enabled: Mapped[bool] = mapped_column(
Boolean, nullable=False, default=True
)
presentation_auto_apply_threshold: Mapped[float] = mapped_column(
Float, nullable=False, default=0.90
)
presentation_conflict_threshold: Mapped[float] = mapped_column(
Float, nullable=False, default=0.50
)
# Default = SigLIP 2 (so400m, 512px) for new installs (migration 0069);
# existing libraries keep their stored value until the operator re-embeds.
embedder_model_version: Mapped[str] = mapped_column(