Authoritative sources
Where the domain facts come from. Check here before hand-entering a species code, a metadata key, or a chunk layout — and add to this list when you find a new source rather than leaving it in a commit message.
Each entry says what the source is authoritative for. That matters more than the URL: several of these overlap in subject and disagree in detail.
Species codes and names
| Source | Authoritative for | Notes |
|---|---|---|
| Wildlife Acoustics — Bat Auto-ID Supported Species and Abbreviated Codes | The codes the Echo Meter Touch and Kaleidoscope actually emit. This is the vocabulary taxa_eu.yaml and taxa_na.yaml map. |
Species-level only — it defines no genus or group codes. MYOSPP was invented against this list and had to be removed. 31 European species (all mapped) + 38 North American species (all mapped), each cross-checked against Wikipedia/IUCN/ASM for current scientific and common names — 2026-09-04. |
| NABat — List of Species Codes | North American Bat Monitoring Program codes, and its non-species/group codes (verified 2026-09-05, for the manual-classification feature: MYSP = "Unknown species in the Myotis genus" / Myotis sp.; HighF/HiF = "pulses having a minimum frequency higher than ~30 kHz"; LowF/LoF = the lower-than-~30kHz equivalent; Hilo = "Two or more bats from distinct frequency classes vocalizing simultaneously within a recording" — note the capitalization, not HiLo; NOTBAT = "Not a bat", explicitly distinct from NoID = "Bat, but no grouping or user-defined category applies"). |
Lists two codes per species: a four-letter and a six-letter form (Eptesicus fuscus is both EPFU and EPTFUS). Both belong to one authority — see the note below before treating them as separate sources. No equivalent genus-grouping code exists for Plecotus (checked directly against the page, 2026-09-05) — expected, since NABat covers North America only and Plecotus doesn't occur there. |
Naming — there is no official name for these code systems. Searched for one; none found. Each authority uses only descriptive terms for its own: NABat says "four-letter species code" / "six-letter species code" (its column headers), Wildlife Acoustics says "abbreviated codes" (its page title).
Do not call them "alpha codes". Birds have a real standardized 4- and 6-letter alpha code system in which the four-letter form comes from the English name (American Robin →
AMRO). Both bat forms come from the scientific name:EPFUis genus-2 + species-2,EPTFUSgenus-3 + species-3. Borrowing the bird term would describe them wrongly.The six-letter forms coincide with Wildlife Acoustics'. Every species checkable in both lists matches exactly: Antrozous pallidus
ANTPAL, Eptesicus fuscusEPTFUS, Euderma maculatumEUDMAC.Even so, keep them as separate sources —
nabat4,nabat6, and whatever holds the WA vocabulary. Folding the six-letter codes into the WA source because they currently agree would re-introduce precisely the universal-code-key assumption spec D10 exists to reject, only at smaller scale. The two registries are independently maintained, the construction rule is not injective, and nothing binds them to resolve a collision the same way. Duplicating ~30 rows costs nothing; a silent merge of two authorities' claims is not recoverable afterwards, because the row no longer records who said it.A taxon may hold several codes under one source —
uq_taxon_codeis(source, code), not(source, taxon_id)— soEPFUandEPTFUScan both point at Eptesicus fuscus. Pinned bytest_one_taxon_may_carry_several_codes_from_one_source.A third, suffix-less
nabatsource exists too (taxa_groups.yaml'sMYSP/HiF/LoF/Hilo/NOTBAT) — distinct fromnabat4/nabat6above. Those codes are NABat's non-species group/genus/frequency-class codes, not per-species four- or six-letter codes, so neither species-code source fits;nabat(no suffix) is where they belong instead.
File formats
| Source | Authoritative for | Notes |
|---|---|---|
| GUANO specification (spec document) | The open bat-acoustics metadata standard — the guan RIFF sub-chunk, its UTF-8 Key: Value layout, and the core field names. |
What src/fledermap/ingest/guano_read.py implements. |
| Wildlife Acoustics GUANO Metadata Namespace | The vendor's own WA| -namespaced GUANO extension fields. |
Vendor extensions to the standard above, not a competing format. |
| guano-py | Reference Python implementation of GUANO reading and writing. | Useful for cross-checking our parser's behaviour on edge cases. |
wamd2guano.py |
A reference decoder for the undocumented wamd chunk. |
Wildlife Acoustics never documented wamd; ours was decoded by hex-dumping real files (spec D18). Cross-checked against this independent implementation — see below. |
wamdcross-check — done, R1 substantially narrowed.src/fledermap/ingest/wamd.py's type IDs, derived from two simulator-generated sample files, were compared againstwamd2guano.py'sWAMD_IDStable (fetched 2026-08-24). Five of seven IDs match exactly on both number and meaning:0x01model,0x05timestamp,0x0bauto_id,0x0cmanual_id, and0x06(ours "position", theirs "gpsfirst" — same field, theirs is just more precise about it being the first GPS fix).Two are worth a closer look: -
0x03— ours "app version", theirs "firmware". Different label, plausibly the same slot used differently: on the samples this holds an app build string ("App 3.1.10"), not detector firmware, which fits the two files being simulator output rather than real hardware. Watch this specifically on the first real-hardware recording — it may carry an actual firmware version there instead. -0x04— ours "device", theirs "prefix". This looked like a real disagreement (a filename prefix is a very different thing from a device name), but our own sample data resolves it: the real decoded value at this offset is the literal string"iPhone Simulator", which cannot be a filename prefix (EMT filenames use species codes —EPTSER,NoID,NOISE— as prefixes, never a device name). "device" is the empirically better-supported reading for at least this generation of the format;wamd2guano.py's "prefix" label may reflect a different firmware version or hardware line than these two samples.Both remaining questions are pinned to spec R1 rather than left loose here, and both resolve automatically the moment a real-hardware recording is ingested (phase 0b).
wamdcross-check, continued — real hardware arrived, 2026-08-26. Both open questions above are now settled:0x03does hold a real firmware/app build string on real hardware too ("App 3.1.10", same shape as the simulator), and0x04stayed"Echo Meter Touch 2 Standard Android"— a device name, confirming "device" overwamd2guano.py's "prefix" reading.New finding this cross-check exists to catch: this device's
0x06(position) writes the wrong sign for longitude, verified against real coordinates (Hannover, Germany — positive/east is correct) while the standardguanchunk'sLoc Positionfield gets the same coordinate right in the same file. Checked againstwamd2guano.pyspecifically to rule out a documented sign convention we might be missing: its GPS parser branches on format, and for the plainWGS84,<lat>,<lon>,<elev>layout this device uses (its own "EMTouch format" branch, as opposed to theN/S/E/W-suffixed branch it does negate) it takes the values as literal signed floats, same asingest/wamd.py's_parse_position. The reference decoder reads this field exactly the way we do and still gets the wrong hemisphere — this is the device's own bug, not a convention mismatch in either decoder. Detail and consequences in the design spec's R1 section.
Classifiers (additional identification sources, not yet built)
Per the design, species ID is not this project's problem — the EMT already does
it, and other classifiers are simply further identification rows (spec D9).
- BattyBirdNET-Analyzer
- BattyBirdNET-Pi — the single-station system that prompted this project
- BatDetect2 — no URL recorded yet; add one when it is next referenced
Prior art
Surveyed before starting; neither does what Fledermap does.
Related local projects
Not public; on this machine only.
../poiidx— the user's PostGIS OSM POI index, used to name derived locations. It drops and recreates all its tables on any schema or filter-config mismatch, which is why Fledermap uses a separate database (spec D11). Open, unclaimed follow-up (2026-09-01):services/site_naming.py's candidate-outside-the-site check reprojects into a local UTM CRS and callsshapely.intersects()client-side, because poiidx has no query-time "does this candidate intersect a given shape" capability of its own — onlypoi.py'scoordinatesfield (real polygon/line geometry for way/relation-sourced POIs) andpoiIdx.py's distance-basedget_nearest_pois. A server-sideST_Intersectsagainst thegeographycolumn would be both more correct (true geodesic intersects, no UTM zone-edge distortion) and cheaper than the client-side version. Deliberately not done as part of the Fledermap fix that needed it: poiidx is a real pinned PyPI dependency (SN-1), not an editable local one, so a poiidx change means a release cycle before the Fledermap fix could ship. Confirmed bug inpoiIdx.py'sinit_regions_by_shape, found 2026-09-01 in production (installedpoiidx==0.0.9, not the local checkout — those can differ):if buffer is not None: ... local_shape.convex_hull().buffer(buffer).convex_hullis a shapely property, not a method — calling it with()invokes whatever geometry it returns (aPoint, typically) as if that were itself callable, raisingTypeError: 'Point' object is not callable. This fires for every non-Nonebuffervalue, unconditionally — there is no workaround value, only omittingbufferavoids it. Fledermap'sservices/site_naming.pybriefly passedbuffer=(to fix the region-confinement gap described above) and broke every realname_site_taskrun the moment it merged tomain; no test caught it because every Fledermap test mockspoiidx.get_nearest_poisrather than calling the real package (a real coverage gap, not just a poiidx one — worth an occasional real, non-mocked smoke check against a livepoiidx_bats_dbbefore merging anything that changes how poiidx is called). Fledermap reverted to never passingbuffer(commit onfix/poiidx-buffer-crash, 2026-09-01) until poiidx ships a real fix (local_shape.convex_hull.buffer(buffer), dropping the()) and Fledermap's pin moves to a version that includes it — at which point BOTH this bug note and the region-confinement gap above should be revisited together, since fixing one re-enables fixing the other.../mkmapdiary— the map-first presentation this project's UI is modelled on, and the source of the local-projection clustering approach.