Skip to content

feat(vlm): label confirmation backend, VLM scope policy, detector class, audit and promote gate (#119) - #167

Merged
davidamacey merged 10 commits into
mainfrom
feat/label-confirmation
Oct 9, 2026
Merged

davidamacey merged 10 commits into
mainfrom
feat/label-confirmation

Conversation

@davidamacey

Copy link
Copy Markdown
Owner

Backend half of #119, from docs/design/label_confirmation_plan.md section 6, as five stacked changes plus a refactor commit that keeps the new mappings out of the grandfathered curation_opensearch.py.

  1. Per-project vlm policy (scope all|uncertain|representatives|off, daily budget, sample fraction) at GET/PUT /vlm/policy with optimistic revision; one shared selector used by the worker and the auto-label sweep. Default scope all keeps today's behaviour.
  2. The detector's own class is kept on every item (detector_class_name, detector_class_id, detector_confidence) and never overwritten by the VLM or a human.
  3. detector_disagreements review tab.
  4. Accuracy audit: POST /audit/start, GET /audit/queue, GET /audit/report (stratified deterministic sample, Wilson 95 percent intervals, confusion matrix, insufficient_sample flag).
  5. auto_promote is gated on the audit (409 audit_required / audit_precision_low, force=true bypass logged). Behaviour change: the curation-cluster-refresh daemon promotes nothing until the audit clears a class (CHANGELOG).

All contract changes are additive. Deviations from the plan are recorded in the commit messages and issues: #165 (disagreements sort order), #166 (audit fields on the item wire). The uncertain scope uses only the confidence clause because the plan's wording would select every crop on a default project.

Verified offline: full suite 7134 passed, 14 skipped on the merged result; pre-commit all hooks; frontend contract tests (882) still pass; no existing test dropped.
NOT yet verified: the two Painless scripts (sample_frac hash filter, disagreement comparison) and the representatives msearch against a real OpenSearch, and live steps V1 to V5 of the plan (VLM throughput, scope off writes nothing, representatives call count, human label lock and holdout, audit report vs manual count). The Cropwright frontend half is a separate change.

GET/PUT /vlm/policy limit which crops the continuous worker and the
auto_label VLM stage label (all, uncertain, representatives, off), with a
per-day attempt budget and a stable sample fraction. Both selectors take
their scope clauses from one function. Explicit cluster requests are never
limited. The policy is cloned with the project.

Refs #119
Ingest records detector_class_name (registry-name form), detector_confidence
and, when the label is a registry class, detector_class_id, whatever the
class_resolution. No VLM, classifier or human write touches them, so a
detector-vs-VLM comparison stays queryable. Mapped on new and existing
indexes and served on the item wire.

Refs #119
Lists unvalidated items whose VLM class differs from the class the detector
gave them, sorted by vlm_confidence and then detector confidence. Served by
GET /review/tabs; the query fake gains match_none and registered script
twins for its tests.

Refs #119
POST /audit/start draws a deterministic stratified sample (per detector class,
under a total budget) of machine-labelled crops, GET /audit/queue lists the
ones still waiting for a human, and every human class write on a drawn crop
stamps whether the detector, the VLM, both or neither was right.
GET /audit/report gives per-class precision with Wilson 95% intervals, the
confusion matrix and insufficient_sample flags.

Refs #119
POST /clusters/auto_promote and the auto_label promote stage promote a class
only when the audit holds enough human-labelled crops of it and the detector's
audited precision clears promote_min_precision (default 0.95); otherwise 409
audit_required or audit_precision_low names the classes and nothing is
written. force=true bypasses the gate and is logged distinctly; a dry run is
never gated.

Refs #119
…_opensearch.py

The grandfathered module keeps its line count; the detector and audit field
mappings, the policy-document mapping and their ensure helpers live in
curation_opensearch_items.py.

Refs #62, #119
…M selection

The worker and the auto_label sweep now share the cluster-scope exclusions, so
no scope selects a test_holdout or class_excluded item.

Refs #119
The id carried only the UTC second, so two starts in one second shared it.
It now has microseconds and a random suffix and still sorts by time.

Also documents that sample_frac hashes the crop id.

Refs #119
@davidamacey
davidamacey merged commit ac8ae00 into main Oct 9, 2026
10 checks passed
@davidamacey
davidamacey deleted the feat/label-confirmation branch October 9, 2026 23:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant