Skip to content

Show which models have feature vectors on an occurrence, and filter occurrences by them - #1525

Draft
mihow wants to merge 19 commits into
feat/detection-embeddings-taskfrom
feat/feature-vectors-in-filters-and-ui
Draft

mihow wants to merge 19 commits into
feat/detection-embeddings-taskfrom
feat/feature-vectors-in-filters-and-ui

Conversation

@mihow

@mihow mihow commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

#1462 stores a feature vector for every detection, from any number of models, but on its own nothing in the interface mentions them. This PR is the small visible part on top: the occurrence page says which models have vectors for an occurrence, the occurrence list can be filtered by a model that only produces vectors (such as BioCLIP), and a job's "View occurrences" list includes occurrences that the job only added vectors to.

It is split out of #1462 so the storage could be reviewed and merged on its own. It needs both #1462 (the vector table) and #1518 (the shared query that the occurrence algorithm and job filters use), and merges after both.

List of Changes

# Change (what it does for users) How (implementation)
1 The occurrence Fields tab shows which models have feature vectors for the occurrence embedding_algorithms on the occurrence detail response, one query; a "Feature vectors" row in the UI
2 Occurrence lists and exports stay as fast as before the field is only on the detail response, and exports leave it out (EXPORT_EXCLUDED_FIELDS), since it would cost a query per occurrence
3 The occurrence algorithm filter finds occurrences by a model that only produces vectors build_processed_by_algorithm_q() (#1518) gains a vector EXISTS, added only when the selected algorithms have vectors
4 Those models are offered in the algorithm filter's choices Algorithm.objects.used_in_project() adds a vector id list, read through the vector table's own project column
5 A job's "View occurrences" list includes occurrences the job only added vectors to build_created_or_updated_by_job_q() (#1518) gains a vector EXISTS, served by the partial (job, detection) index from #1462

Related Issues

Detailed Description

Why the vector term is conditional

Postgres cannot plan EXISTS ... OR EXISTS ... as a semi-join, so ORing a term per table into the algorithm filter slows the common case of filtering by one classifier. The builder adds the vector EXISTS only when the selected algorithms have vectors, the same rule #1518 uses for algorithm results, so filtering by one classifier stays a single EXISTS.

Measured on #1462 with EXPLAIN (ANALYZE, BUFFERS), medians of 5 runs, read-only. They used a developer machine's copy of production data (projects of 179k and 45k occurrences, main tables only) and a small integration database that has the vector and result tables (21k detections, 30k vectors). Selecting one classifier on the 45k-occurrence project: page 1 takes about 0.8 ms on main, about 240 ms with one EXISTS per output table joined by OR, and about 0.8 ms with the conditional terms. The OR figure is approximated, because the production copy has no vector table. The counts are 223 ms, 461 ms and 138 ms. On the 179k-occurrence project, the OR shape took about 700 ms to count and the conditional shape takes 187 ms. A selection that mixes a classifier and an embedding model still needs two OR terms, and page 1 takes 19.5 ms on the small database. Timings for the vector table at production scale are not measured, because the table does not exist in the production copy yet.

Tests

  • TestEmbeddingsInOccurrenceApi (ami/ml/test_detection_embeddings.py): the detail field lists each algorithm with a vector, costs one query whatever the number of algorithms, and is not on the list view; the algorithm filter finds and excludes occurrences by their vectors, including a vector stored after a cached answer.
  • TestOccurrenceJobFilter.test_vector_only_link_to_a_job_matches and TestOccurrenceAlgorithmChoices (vector-only model offered; vectors in another project do not leak).
  • DataExportTest.test_json_export_leaves_out_the_excluded_fields: exported rows never carry the field and its query never runs.

How to Test

  1. docker compose -f docker-compose.ci.yml run --rm django python manage.py test ami.ml.test_detection_embeddings ami.main.tests.TestOccurrenceJobFilter ami.main.tests.TestOccurrenceAlgorithmFilterQuerySet ami.ml.tests.TestOccurrenceAlgorithmChoices ami.exports.tests.
  2. Run a pipeline whose processing service returns embeddings on each detection (ami-data-companion Bump react-admin from 4.8.4 to 4.11.4 in /frontend #175 with features_for_all_detections on), then open one of its occurrences: the Fields tab lists the models under "Feature vectors".
  3. In the occurrence list, filter by an embedding-only model (for example BioCLIP): only occurrences with its vectors remain, and the model appears in the filter's choices.

Screenshots

Feature vectors row on the occurrence Fields tab

Checklist

🤖 Generated with Claude Code

https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy

mihow and others added 3 commits October 15, 2026 14:03
Builds on the vector storage branch (#1462) and on the shared occurrence results query (#1518):

- the occurrence algorithm and job filters find occurrences whose only result from an
  algorithm or job is a feature vector; the algorithm filter adds the vector EXISTS only when
  the selected algorithms have vectors, so a classifier selection stays one EXISTS;
- feature extractors are offered in the algorithm filter's choices;
- the occurrence detail page lists the algorithms with a vector for the occurrence, in one
  query; the field is left out of the list view and of exports.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
…yment changes project

Algorithm results copy their project from the occurrence, but
Deployment.update_children() only realigned events, occurrences and source images.
After a deployment moved, the algorithm choices for a project (which read the
result's project) listed the old project instead of the new one. Also rewraps the
algorithm filter docstring.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
…nces)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy

# Conflicts:
#	ami/main/api/views.py
@netlify

netlify Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for antenna-preview canceled.

Name Link
🔨 Latest commit 9c4e8b3
🔍 Latest deploy log https://app.netlify.com/projects/antenna-preview/deploys/6adbaf9b1509997769fcb748

@coderabbitai

coderabbitai Bot commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

mihow and others added 2 commits October 15, 2026 15:43
…per filter

The algorithm filter and the job filter shared one builder that switched on
which keyword argument was passed. They share no code path, so each now has
its own function named after the queryset method it serves:
build_processed_by_algorithm_q(algorithm_ids) and
build_created_or_updated_by_job_q(job_id).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
… branch

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
mihow and others added 11 commits October 15, 2026 15:45
…the vector filters branch

Adds the feature vector terms to both new builders: the conditional EXISTS in
build_processed_by_algorithm_q and the job term in
build_created_or_updated_by_job_q.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
…) into the vector filters branch

Keeps both update_children fixes: algorithm results from #1518 and feature
vectors from #1462.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
…the vector filters branch

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
…filters branch

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
…realignment) into the vector filters branch

Algorithm results join the update_children table with their path through
occurrences, and the occurrence serializer's vector lookup drops the key.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
Keeping copied project columns aligned when a station moves belongs with the
move command (#1192) and a project-consistency check (#1453), which may use a
database cascade, not with every deployment save. A moved station's algorithm
results keep their old project until then, which affects only the algorithm
filter's choices.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
… into the vector filters branch

update_children is back to main's version here too; algorithm results and
feature vectors are left to the move command (#1192) and a
project-consistency check (#1453).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
…ft out of update_children) into the vector filters branch

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
…aired detections) into the vector filters branch

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
…vector filters branch

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
mihow added a commit that referenced this pull request Oct 16, 2026
…ranch) into the similarity sort

The sort builds on the filters and the occurrence's embedding_algorithms
that moved to #1525, so it now follows that branch. The vector table has no
key column, so the representative vector and the default model take an
algorithm only; the occurrence filter queries come from #1525's builders.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121zMVjnPsqeDFSBXRCPvMy
mihow and others added 3 commits October 23, 2026 11:31
…ccurrence result query branch

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RPrBDvE8e49rB4o811zHZi
…vector filters branch

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RPrBDvE8e49rB4o811zHZi
…e vector filters branch

#1462 keeps the first vector stored and no longer has DetectionEmbedding.objects.store()
or a reader module, so the tests here insert vectors directly, with the capture's
project and declared vector lengths. The docs take #1462's text, with the three
reads this branch adds (filter choices, the occurrence detail, the algorithm filter)
added to its read table.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RPrBDvE8e49rB4o811zHZi
mihow added a commit that referenced this pull request Oct 23, 2026
…) into the similarity sort

#1462 removed the reader module, so it comes back here with only what the sort
calls: vector counts per algorithm, the algorithm with the most vectors (the
sort's default model) and an occurrence's representative vectors. Their tests
join TestReadingVectors. The similarity tests insert vectors directly, with
declared lengths and the capture's project, as DetectionEmbedding.objects.store()
is gone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RPrBDvE8e49rB4o811zHZi
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