Skip to content

Support querying and displaying circuits by derivation type#642

Open
pgetta wants to merge 4 commits into
mainfrom
feat/circuit-derivation-type-filter-expand
Open

Support querying and displaying circuits by derivation type#642
pgetta wants to merge 4 commits into
mainfrom
feat/circuit-derivation-type-filter-expand

Conversation

@pgetta

@pgetta pgetta commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the backend for openbraininstitute/prod-explore-functionality#517 — the Circuits overview needs a "Derivation type" column and a filter by derivation type, general enough to absorb new derivation types.

This is derived entirely from the existing normalized Derivation table (usedgenerated, indexed derivation_type).

Filter — narrow the circuit list by derivation type

Both directions are exposed, mirroring the Derivation model's roles:

  • ?generated_derivation__derivation_type=circuit_extraction — circuits that were derived this way ("how it was derived")
  • ?used_derivation__derivation_type=circuit_extraction — circuits that are a source of such a derivation ("what was derived from it")
  • __in variants supported for both.

No source-type restriction: a circuit derived from a non-circuit source (e.g. emodel_circuit) surfaces that type too. Implemented with the established query_params_factory + filter_joins join pattern (enum, so it's a plain filter, not a facet).

Read — opt-in derivation lists via expand

Derivation data loads only when requested, per direction, via the expand query param (same pattern as cell_morphology / em_cell_mesh):

  • ?expand=generated_derivations → each entry carries the source used entity
  • ?expand=used_derivations → each entry carries the resulting generated entity
  • both can be requested together (repeated params: ?expand=generated_derivations&expand=used_derivations)

Backed by two view-only relationships on Circuit plus load-aware properties: an un-expanded direction serializes as null (no extra query, never trips raiseload); an expanded-but-empty direction serializes as []. Nothing loads on the default GET /circuit.

Pagination fix (one-to-many filter joins)

The derivation filter join is one-to-many (a circuit can match several Derivation rows). The shared paginated id subquery (_with_subquery) selected ids without DISTINCT, so OFFSET/LIMIT paged over duplicated rows while total_items used count(distinct id) — a matching circuit could repeat across pages or be skipped. Fixed by selecting DISTINCT ids in the subquery, which also hardens the pre-existing contribution / mtype / used / generated one-to-many filters. DISTINCT is valid because every ORDER BY element is already in the subquery select list.

pgetta added 2 commits June 25, 2026 09:06
Expose circuit derivations on GET /circuit, derived from the existing
Derivation table (no migration, no denormalized column).

- Filter (always available), both directions mirroring the read fields:
  generated_derivation__derivation_type (circuit is the generated/derived
  entity) and used_derivation__derivation_type (circuit is the used/source
  entity), each with an __in variant. No source-type restriction, so e.g.
  an emodel->circuit emodel_circuit derivation matches on the generated side.
- Read columns (opt-in via ?expand=...): generated_derivations and
  used_derivations, expandable independently. Unexpanded directions are
  omitted; expanded but empty serialize as []. Load-aware properties keep
  this safe under raiseload("*") via two viewonly relationships on Circuit.
The paginated id subquery in `_with_subquery` selected ids without DISTINCT,
so a one-to-many filter join (e.g. the circuit derivation-type filters, where a
circuit can match several Derivation rows) duplicated the row. OFFSET/LIMIT then
paged over the duplicated rows while total_items used count(distinct id), so a
matching entity could repeat across pages or be skipped.

Select DISTINCT ids in the subquery so the limit window operates on distinct
entities. This also hardens the pre-existing contribution/mtype/used/generated
one-to-many filters. DISTINCT is valid here because every ORDER BY element is
already part of the subquery select list.

Add a regression test asserting a circuit matched by multiple derivation rows is
counted once and does not reappear on a second page.
@pgetta pgetta self-assigned this Jun 25, 2026
@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Flag Coverage Δ
pytest 97.76% <100.00%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
app/db/model.py 99.16% <100.00%> (+0.01%) ⬆️
app/filters/circuit.py 100.00% <100.00%> (ø)
app/queries/common.py 98.19% <100.00%> (ø)
app/queries/factory.py 100.00% <100.00%> (ø)
app/schemas/circuit.py 100.00% <100.00%> (ø)
app/service/circuit.py 100.00% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread app/service/circuit.py
query = query.options(
selectinload(Circuit.derivations_as_used).joinedload(Derivation.generated)
)
return query

@GianlucaFicarelli GianlucaFicarelli Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For discussion: it would be nice if the possibility to filter by derivation type and get the list of derivations is made available to any entity.
However, it would require changes:

  • to the _load function in every entity (change to many files, with many repetitions, so I would avoid that)
  • or some changes in queries/common.py for read_many/read_one

Thoughts? Could it be useful already for other derivation types?
If the change adds too much complexity, the generalization can be postponed.

Comment thread app/service/circuit.py
def read_one(
user_context: UserContextDep,
db: SessionDep,
id_: uuid.UUID,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can it be useful to add the expand parameter to read_one as well, not only read_many?
For cell morphologies, it's present in read_one and not in read_many (for performance reasons), but it would be cleaner to be consistent across endpoints.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, I've extended also the read_one

pgetta added 2 commits June 25, 2026 16:52
Wire the `expand` query param into read_one and admin_read_one, mirroring
read_many/admin_read_many: switch the response schema to CircuitExpandedRead
when expand is set and pass partial(_load, expand=expand). Add a test
exercising expand on both the user and admin get-one routes.
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.

2 participants