Search Klimatdatabas inside one process. FTS, vectors, REST, MCP, and a console on the binary you run.

Generic Swedish construction resources, bilingual: typical and conservative A1–A3, A4, A5.1, transports. Danish BR is a second Catalog (dkbr), file-only. Agents call REST/MCP; humans open GET / after make run. This Pages site is not that console. Cite Boverket. Not affiliated with Boverket.

$ git clone https://github.com/mong-x/klimatsearch.git && cd klimatsearch && make run
http

One binary. Search, compare, and serve agents.

stdlib ServeMux, CGO SQLite, goose migrations, official MCP Go SDK. Kind e2e on every main push. 1.0 How it works →

Hybrid Hits
FTS5 BM25 always. sqlite-vec KNN when vectors exist. Reciprocal Rank Fusion k=60. match_source is retrieval fts | vector | both; rerank is a separate flag on the Hit.
Catalog identity
Primary key is (catalog_id, resource_id). Catalogs: boverket (JSON API) and dkbr (file; DatasetVersion BR18/BR25). br25 aliases dkbr. REST databases= filters Catalogs — not a citation.
MCP tools
search_climate_data, get_resource_details, compare_resources on /mcp (streamable) and /mcp/sse.
Self-hosted
Default binary has no Unkey and no MPP. /healthz and /api/* are ungated. The hosted build we run adds Guard via -tags hosted.
Operator console
html/template on the running process: Query, Data, Ingest, Webhooks, Lab. Open http://127.0.0.1:8080/ after make run. This Pages site is marketing only.

Console on the process. Not this site.

After make run, open GET /. Same Engine as REST and MCP. 1.1 Console →

GETWhat it is
/Query — catalogs, vector, rerank, rank, REST/MCP inspector
/dataSQLite coverage, Resources, embeddings
/ingestPreview a CSV/xlsx, map columns, Apply (file stashed 30 min)
/compareTwo Resource IDs
/connectLab — /healthz, mcp.json, curl
/webhooksAdd and edit Slack, Discord, and JSON destinations

Webhooks. Slack, Discord, and JSON together.

On the process: GET /webhooks. Edit URL, events, secret, enabled. Env KLIMAT_WEBHOOK_URL is extra, not a replacement. 1.2 Notify →

EventWhen
catalog.changedIngest upserted a Resource whose ContentHash changed
catalog.unreachableBoverket JSON and Excel both fail
ingest.failedBoot or weekly ingest errors
DestinationBody
Slack hooks.slack.com{"text":"klimatsearch catalog.unreachable …"}
Discord discord.com/api/webhooks{"content":"…"}
Anything elseJSON Event; optional HMAC X-Klimat-Signature

The surface agents actually call. REST, MCP, then payment.

Same Guard on /api/*, /mcp, and /admin/*. 2.0 API →

MethodPath
GET/healthz
GET/openapi.yaml
GET/docs
GET/api/search?q=&vector=&rerank=&lang=sv|en&databases=
GET/api/resources/{id}?lang=
GET/api/resources/compare?a=&b=&unit=
POST/admin/ingest/file?catalog=dkbr
POST/admin/ingest/preview
POST/admin/resources
ToolDoes
search_climate_dataQuery names and descriptions; optional databases[]
get_resource_detailsOne Resource by prefixed or bare id
compare_resourcesA1–A3 of two Resources in one unit
BuildWhat you get
make buildSelf-hosted. No Unkey, no mpp-go. API is ungated.
make build-hostedOur process. MPP Payment (402) and Unkey Bearer.

Numbers you can check. Not a dashboard mockup.

From the official Klimatdatabas 02.07.000 JSON view and this repo’s tests. 3.0 Specs →

FactValue
Resources in 02.07.000~230 bilingual rows (208 products with typical A1–A3, 22 energy/fuel)
EmbeddingF2LLM-v2-80M, 320-d, last-token pool, L2; Fake in CI
RRF k60
Go1.27, CGO, -tags fts5
LicenseApache-2.0 for software; cite Boverket for data

Data is from Boverket Klimatdatabas. You must cite Boverket as the source. This project is not affiliated with Boverket. Production weights for the hosted process are documented in docs/LAUNCH.md — they are not in git.