Files
hagfish/README.md
T
2026-08-27 10:40:22 +01:00

3.4 KiB

Hagfisk

Faroese fisheries data pipeline and dashboard. Pulls monthly landing statistics from Hagstova Føroya's PX-Web API, stuffs them into a local DuckDB, and serves a filtered ECharts dashboard — all from a single Rust binary.

Named after the hagfish: a deep-sea creature famous for producing copious amounts of slime and being generally unpleasant. The name stuck anyway. The fish has no backbone. Neither does this README.

What It Does

Watch demo


  • Fetches fisheries data (mass + value by species, gear, zone, month) from statbank.hagstova.fo
  • Stores everything in a local DuckDB file
  • Serves a REST API with filtered queries, summaries, and Parquet export
  • Renders an interactive dashboard with line, price-trend, and stacked bar charts
  • Runs monthly ingestion automatically via systemd timer (28th of each month, 03:00)

Tech Stack

Thing Choice Why
Language Rust 2024 Fast, safe, single static binary. No runtime, no VM, no nonsense.
Web framework Axum Async, typed, plays well with Tokio. Doesn't fight you.
Database DuckDB Embedded OLAP. No server, no config, just a file. Perfect for analytics on a single machine.
Charts ECharts Powerful, flexible, no build step. Vanilla JS, no framework tax.
Static assets rust-embed Compiled into the binary. One artifact to deploy, nothing to forget.
Logging tracing + tracing-subscriber Structured logs to file and journal.
Deployment Bare metal + systemd No containers. No compose files. No port mappings. Just a binary and a .service unit.

Getting Started

cargo build --release
./target/release/hagfisk ingest --full
./target/release/hagfisk serve

Dashboard lives at http://localhost:8090.

Configuration

config.json next to the binary:

{
  "duckdb_path": "hagfish.db",
  "bind_address": "127.0.0.1:8090",
  "data_source_url": "https://statbank.hagstova.fo/api/v1/fo/H2/VV/VV01/fisknv_md.px",
  "log_file_path": "hagfish.log",
  "allowed_origins": []
}

CLI

hagfisk serve              Start the web server
hagfisk ingest [--full]    Fetch data (incremental or full backfill)
hagfisk export -o FILE     Export landings to Parquet

Deployment

The justfile handles everything, but you'll need to install it on your machine.

just deploy            # Build, rsync, restore SELinux contexts, restart
just deploy-reingest   # Same + full re-ingest
just logs-app          # Tail application logs
just ps-app            # Check service status

Systemd units live in systemd/ — drop them in ~/.config/systemd/user/ and enable the timer for monthly ingestion.

API

Endpoint What
GET /healthz Alive check
GET /api/species Species codes + Faroese names
GET /api/zones Economic zone codes
GET /api/gear Fishing gear codes
GET /api/landings Filtered landing records (month range, species, gear, zone, measure)
GET /api/summary Monthly aggregates, top 10 species, price/kg trend
GET /api/summary/monthly-breakdown Per-month per-species breakdown
GET /api/available-filters Cascading filter options based on active selections
GET /api/export.parquet Full dataset as Parquet download

Full PX-Web API reference in docs/API.md.


Like the hagfish itself: not glamorous, but it works.