Skip to content

[FEATURE] External Species Links on Birds Page #114

@scottchronicity

Description

@scottchronicity

Context

The Birds page shows species by common name only. Users want to click through to authoritative info (photos, range maps, sounds) — All About Birds, eBird, Wikipedia. We can link reliably, but only if we persist the scientific name: our current species_code is a 6-char slug derived from the scientific name, NOT the eBird code, and common-name-only URLs are ambiguous across regions.

Good news: BirdNET already parses the scientific name from labels.json ("Scientific_Name") and includes it in its prediction output — it is simply dropped before persistence. Wiring it end-to-end unlocks deterministic linking.

Outcome: each detection row on the Birds page gains a small set of external-link icons (All About Birds, eBird, Wikipedia) that resolve deterministically for new rows. Historical rows without a scientific name fall back to common-name-based Wikipedia / All About Birds links where possible.

Architecturally Significant Requirements (ASRs)

Interface (Contract):

  • Detection pydantic model (platform/orpheus-common/src/orpheus_common/detection/models.py) gains species_scientific: Optional[str] = None. to_dict() / from_dict() round-trip the field.
  • /api/data/birds/history response detection objects include species_scientific (nullable).
  • New <SpeciesLinks /> React component rendering up to three compact icon links per detection row.

Implementation (Internal Logic):

  • Additive SQLite migration: add species_scientific TEXT column via the existing ensure_schema_updates() hook (auto-runs on startup). No backfill.
  • BirdNET agent wiring: parser in birdnet.py already emits species_scientific (around line 160). Verify the agent main forwards it into the Detection(...) constructor; add a pass-through if missing.
  • Frontend slug helpers: lowercase, replace spaces with - (eBird) or _ (Wikipedia); All About Birds uses its own common-name-based URL format (verify during implementation).
  • Fallback logic when species_scientific is null: render All About Birds + Wikipedia search links keyed on common name; omit the eBird link.

Architectural Constraints

  • No backfill. Historical rows keep species_scientific = NULL. Migration is additive and reversible by dropping the column.
  • Must not break existing Detection consumers — new field is optional with default None.
  • Must be Python 3.9 compatible.
  • Must not introduce new runtime dependencies on either side.
  • External URL formats should be verified against each site's canonical pattern during implementation; keep them in one place (the <SpeciesLinks /> component) so they're easy to update if a site's URL scheme changes.

Acceptance Criteria

Feature: External Species Links on Birds Page

  Scenario: New detection has scientific name and all three links
    Given a BirdNET detection arrives with species_scientific="Corvus brachyrhynchos" and species_common="American Crow"
    When the detection persists and the Birds page row renders
    Then the row shows three icon links: All About Birds, eBird, and Wikipedia
    And clicking Wikipedia opens https://en.wikipedia.org/wiki/Corvus_brachyrhynchos

  Scenario: Historical detection without scientific name falls back gracefully
    Given a historical detection row has species_scientific=NULL and species_common="American Crow"
    When the Birds page renders the row
    Then All About Birds and a Wikipedia search link are rendered
    And the eBird link is NOT rendered (it needs the scientific name)
    And the UI does not crash

  Scenario: Schema migration on startup is idempotent
    Given an existing SQLite detections database with no species_scientific column
    When the backend starts up
    Then ensure_schema_updates adds the species_scientific TEXT column
    And a second startup does not attempt to re-add the column
    And old rows read back with species_scientific = None

Definition of Done

  • Detection model updated with species_scientific: Optional[str] = None and round-trip tests in orpheus-common.
  • SQLite schema migration via ensure_schema_updates(); verified idempotent on an existing DB.
  • BirdNET agent forwards species_scientific from the parser to Detection(...). Agent test updated.
  • /api/data/birds/history returns species_scientific in the detection dicts. Backend test updated.
  • <SpeciesLinks /> React component + integration into Birds.tsx detection rows. Frontend test verifying link URLs for the scientific-present and scientific-NULL cases.
  • End-to-end manual verification: new detection row shows 3 links; historical row shows 2 (fallback).

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions