New Search "Generate query" — fetch a query sequence from NCBI¶
Motivation¶
The New Search query box only offered a fixed set of bundled FASTA examples ("Load example"). Researchers routinely start from an NCBI accession or an organism/gene of interest, exactly the way the NCBI BLAST web form works ("Enter accession number(s), gi(s), or FASTA sequence(s)" + Query subrange). There was no in-dashboard way to turn an accession/organism into a query sequence, so a researcher had to leave the browser, fetch the FASTA elsewhere, and paste it back.
Researcher-workflow research (why accession-first, not gene-symbol)¶
Probing NCBI directly showed that a free-text "gene name → coordinates" mapping is unreliable for the viral records this dashboard targets:
F3L(vaccinia-style symbol) returns 0 hits in the Gene DB.- The ortholog symbol
OPG057resolves, but to a different locus than the F3L query the repo already ships, and poxvirus RefSeq records label genes byOPG###only — theF3Llabel is not present in the record at all.
NCBI BLAST, Batch Entrez, and datasets are all accession/identifier-first.
So the modal mirrors that: search to find an accession, then pick a gene
feature (browsed from the record's own feature table) or a manual sub-range —
no fragile gene-symbol auto-mapping.
User-facing change¶
- New "Generate query" button next to "Load example" in the New Search query section (gated on a selected database, same as the other actions).
- Opens a modal that:
- searches
db=nuccoreby organism/keyword/accession and lists candidate records (accession, length, RefSeq flag, organism/title); - lets the researcher enter/select an accession and Load genes to browse the record's gene/CDS features (name, product, locus_tag, coordinates, strand) with a filter box;
- accepts a manual sub-range (From/To) + strand toggle, previews the FASTA
header (
:cSTOP-STARTfor the minus strand), and inserts the fetched FASTA into the query box (switching the program toblastnand refreshing an untouched auto title). - All NCBI traffic is proxied through the api sidecar; the browser never calls NCBI directly.
NCBI API key (Settings plumbing, key-ready)¶
Per the request to "wire it so a key saved in Settings later is used":
ncbi_identity_params()now resolves the API key via_resolve_api_key(): the deploy-timeNCBI_API_KEYenv wins; when unset it falls back to a Settings store (api/services/ncbi_pref.py, single deployment row, masked reads only — the plaintext key is never returned to the browser)._rate_capacity()also consults_resolve_api_key(), so a key saved in Settings genuinely lifts the shared token bucket from 3 → 10 req/s (not just theapi_keyURL param). Surfaced by the self-critique pass — without it a Settings-only key would have passedapi_keyto NCBI yet stayed self-throttled at 3 req/s.- New auth-gated routes
GET/PUT /api/settings/ncbipersist/clear and read the masked status, with typed clientssettingsApi.getNcbiKey/putNcbiKey. The Settings panel input UI is intentionally deferred; the end-to-end "saved key is honoured" path is complete and unit-tested.
API / IaC diff summary¶
- New backend service
api/services/ncbi/search.py—search_nuccore(esearch + esummary) andfetch_feature_table(rettype=ft parse, gene/CDS product merge, plus/minus strand, coordinate normalisation, byte + feature caps). - New routes in
api/routes/ncbi.py:GET /api/ncbi/search,GET /api/ncbi/nuccore/{accession}/features(per-caller quota + shared NCBI rate bucket reused; FASTA fetch reuses the existing/api/ncbi/nuccore/{accession}/fastawithseq_start > seq_stopfor the minus strand — no fasta-route change). - New
api/services/ncbi_pref.py+api/routes/settings/ncbi.py(registered in the settings aggregator). api/services/ncbi/_eutils.py:ncbi_identity_paramsgains the env→store key resolver (backward-compatible; signature unchanged).- Frontend:
web/src/api/ncbi.ts(searchNuccore,getNuccoreFeatures+ types),web/src/api/settings.ts(getNcbiKey/putNcbiKey+NcbiKeyStatus),web/src/pages/blastSubmit/SequenceBuilderDialog.tsx(modal + extracted pure helpers),web/src/pages/blastSubmit/QuerySection.tsx(button + insert handler + modal wiring). - No IaC change.
Validation evidence¶
- Backend:
uv run ruff check apiclean;uv run pytest -qontest_ncbi_search.py(new),test_ncbi_pref.py(new),test_ncbi_nuccore.py,test_route_contracts.py,test_persona_matrix.py,test_settings_service_bus.py→ 169 passed. - The new
test_ncbi_search.pyre-verifies the F3L feature span: a minus-strand gene at46483..46022parses to a 462 bp feature, matching the checked-inMPXV_F3L.faquery. - Frontend:
npm run buildsucceeds;eslintclean on the new/changed files;vitest run src/pages/blastSubmit→ 212 passed, includingSequenceBuilderDialog.test.ts(9 tests: minus-strand coordinate swap +:cSTOP-STARTheader). - Live NCBI cross-check (manual, during design): re-fetching
NC_063383.1seq_start=46022 seq_stop=46483 strand=2reproduced the checked-inMPXV_F3L.fabyte-for-byte.
Follow-up fix — large feature tables (same day)¶
First live use surfaced a HTTP 422 when "Load genes" was run against a
bacterial genome: the feature-table byte cap was 1 MiB but a ~5k-gene genome's
table is ~2 MB (e.g. AP019314.1 = 2.08 MB). Fixes:
- Raised
MAX_FEATURE_TABLE_BYTES1 → 6 MiB (covers bacterial/viral/organelle records); a chromosome-scale record that still exceeds it returns a friendlyncbi_features_too_many422 ("Enter a sub-range manually instead.") rather than the raw "response too large". - The modal now surfaces the backend error
message(api/client.ts only fills.body, not.error, so the toast had shown the generic "HTTP 422"). - Live re-verified: Load genes on
AP019314.1now renders the gene list (product + strand + coordinates) with zero error toasts.