Activity search + RecoverySweep fix + tagger_predictions shrink (#762, #764) + backup polish #91

Merged
bvandeusen merged 6 commits from dev into main 2026-06-10 14:33:35 -04:00
3 changed files with 42 additions and 6 deletions
Showing only changes of commit c8b815afe6 - Show all commits
+15 -2
View File
@@ -9,7 +9,7 @@ from sqlalchemy import delete, select
from sqlalchemy.dialects.postgresql import insert from sqlalchemy.dialects.postgresql import insert
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from ...models import Tag, TagAllowlist, TagSuggestionRejection from ...models import MLSettings, Tag, TagAllowlist, TagSuggestionRejection
from ...models.tag import image_tag from ...models.tag import image_tag
from .aliases import AliasService from .aliases import AliasService
@@ -91,12 +91,25 @@ class AllowlistService:
) )
await self.dismiss(image_id, tag_id) await self.dismiss(image_id, tag_id)
async def _store_floor(self) -> float:
return (
await self.session.execute(
select(MLSettings.tagger_store_floor).where(MLSettings.id == 1)
)
).scalar_one()
async def update_threshold( async def update_threshold(
self, tag_id: int, min_confidence: float self, tag_id: int, min_confidence: float
) -> None: ) -> None:
row = await self.session.get(TagAllowlist, tag_id) row = await self.session.get(TagAllowlist, tag_id)
if row is not None: if row is not None:
row.min_confidence = min_confidence # An allowlist tag can't auto-apply more permissively than the
# ingest store floor — predictions below tagger_store_floor aren't
# stored, so a lower min_confidence would behave identically to the
# floor. Clamp so the stored threshold matches actual behavior
# (#764).
floor = await self._store_floor()
row.min_confidence = max(min_confidence, floor)
async def remove(self, tag_id: int) -> None: async def remove(self, tag_id: int) -> None:
await self.session.execute( await self.session.execute(
@@ -10,7 +10,7 @@
<v-text-field <v-text-field
:model-value="item.min_confidence" type="number" :model-value="item.min_confidence" type="number"
density="compact" hide-details style="max-width: 100px;" density="compact" hide-details style="max-width: 100px;"
min="0.05" max="1" step="0.05" :min="floor" max="1" step="0.05"
@update:model-value="(v) => onThreshold(item.tag_id, v)" @update:model-value="(v) => onThreshold(item.tag_id, v)"
/> />
</template> </template>
@@ -25,23 +25,32 @@
</template> </template>
<script setup> <script setup>
import { onMounted } from 'vue' import { computed, onMounted } from 'vue'
import { useAllowlistStore } from '../../stores/allowlist.js' import { useAllowlistStore } from '../../stores/allowlist.js'
import { useMLStore } from '../../stores/ml.js'
const store = useAllowlistStore() const store = useAllowlistStore()
const ml = useMLStore()
// min_confidence can't be set below the tagger store floor — predictions
// below it aren't stored, so a lower threshold would behave identically to
// the floor. The backend clamps too (#764).
const floor = computed(() => ml.settings?.tagger_store_floor ?? 0.70)
const headers = [ const headers = [
{ title: 'Tag', key: 'tag_name', sortable: true }, { title: 'Tag', key: 'tag_name', sortable: true },
{ title: 'Kind', key: 'tag_kind', sortable: true, width: 110 }, { title: 'Kind', key: 'tag_kind', sortable: true, width: 110 },
{ title: 'Min confidence', key: 'min_confidence', sortable: false, width: 140 }, { title: 'Min confidence', key: 'min_confidence', sortable: false, width: 140 },
{ title: '', key: 'actions', sortable: false, width: 60 } { title: '', key: 'actions', sortable: false, width: 60 }
] ]
onMounted(() => store.load()) onMounted(() => {
store.load()
if (!ml.settings) ml.loadSettings()
})
let debounce = null let debounce = null
function onThreshold(tagId, value) { function onThreshold(tagId, value) {
if (debounce) clearTimeout(debounce) if (debounce) clearTimeout(debounce)
debounce = setTimeout(() => { debounce = setTimeout(() => {
const v = parseFloat(value) const v = Math.max(parseFloat(value), floor.value)
if (v > 0 && v <= 1) store.updateThreshold(tagId, v) if (v > 0 && v <= 1) store.updateThreshold(tagId, v)
}, 500) }, 500)
} }
+14
View File
@@ -104,3 +104,17 @@ async def test_update_threshold_and_remove(db):
assert abs(row.min_confidence - 0.80) < 1e-6 assert abs(row.min_confidence - 0.80) < 1e-6
await svc.remove(tag.id) await svc.remove(tag.id)
assert await db.get(TagAllowlist, tag.id) is None assert await db.get(TagAllowlist, tag.id) is None
@pytest.mark.asyncio
async def test_update_threshold_clamped_to_store_floor(db):
# A min_confidence below the store floor (default 0.70) is clamped up —
# predictions below the floor aren't stored, so a lower threshold can't
# apply more permissively than the floor (#764).
tag = await TagService(db).find_or_create("Lowthr", TagKind.general)
svc = AllowlistService(db)
img = await _make_image(db)
await svc.accept(img.id, tag.id)
await svc.update_threshold(tag.id, 0.30)
row = await db.get(TagAllowlist, tag.id)
assert abs(row.min_confidence - 0.70) < 1e-6