# Species catalog generator

Regenerates [`assets/catalog/species.json`](../assets/catalog/species.json) — the
bundled, offline, multilingual catalog of edible / cultivated plant species the
app seeds into the encrypted database at first run.

```sh
cd apps/app_seeds
dart run tool/gen_species_catalog.dart        # writes species.json (version 3)
dart run tool/gen_species_catalog.dart --dry  # prints stats, writes nothing
```

The generated JSON is **committed** to the repo — the app never queries the
network for it (local-first, offline).

## Sources & licensing

- **Names** come only from **Wikidata** labels and aliases. Wikidata is **CC0**
  (public domain), so the bundled data carries **no attribution burden** and is
  clean for redistribution under the app's AGPL-3.0 license.
- **`gbif_key`** links the [GBIF](https://www.gbif.org/) taxonomic backbone. It
  is used **only as an identifier** (a fact, not copyrightable text) — no GBIF
  vernacular text is bundled, so no CC-BY attribution is triggered.
- **Curated core crops** (names, family and `viability_years`) live in
  [`curated_overrides.json`](curated_overrides.json), hand-maintained. For a
  listed species the curated common names **win** (placed first, never dropped)
  and its family/viability override Wikidata; Wikidata still adds more languages.
  A curated species is always kept even if it would miss the relevance floor.
- **`viability_years`** (seed longevity) is **not** in Wikidata — it comes only
  from the curated overrides (public-domain agricultural-extension figures).
  Species without an entry simply get no age-based expiry warning.

## What it does

1. **Phase 1 — species list (SPARQL):** taxa with a binomial name that are a
   *crop* (`P279* Q235352`), the source/ingredient of a *food*
   (`P1672`/`P366`/reverse `P1582` → `P279* Q2095`), with scientific name
   (`P225`) and GBIF key (`P846`).
2. **Phase 2 — names + family:** `rdfs:label` (primary) + `skos:altLabel`
   (aliases), batched over the species and filtered to the bundled languages;
   the family is walked up `P171*` to the family-rank ancestor.
3. **Transform (`buildCatalog`, pure & unit-tested):** dedup by scientific name;
   drop non-vernacular noise (the binomial itself, author citations, `var.`/`×`
   ranks, abbreviated initials); merge the curated overrides (authoritative,
   first); require a real vernacular in **≥ 4 languages** as a relevance floor so
   the long tail of obscure single-label taxa is dropped; sort; serialize.

The current run yields **~1200 species** (`assets/catalog/species.json` ≈ 1.5 MB).

## Bundled languages

`es, en, fr, de, it, pt, ca, gl, eu, ar, zh, ja, ru` — a multilingual **seed**,
not a ceiling (Latin + Arabic RTL + CJK + Cyrillic). Edit `kCatalogLanguages` in
[`gen_species_catalog.dart`](gen_species_catalog.dart) to widen coverage, then
regenerate. A species with no name in any bundled language is dropped (add its
language rather than assuming es/en).
