# Job: lange lister i baggrunden

Send en hel kundeliste, en fil med koordinater eller tusindvis af BFE-numre som ét job. Grundfast slår hver række op i baggrunden, og du henter resultatet og fejlene som to filer.

Et job er til lister, der er for lange til et [batch-kald](https://grundfast.dk/docs/batch-og-eksport). Hver række slås op med de samme opslag som enkeltkaldene, og svaret lægges ved siden af dine egne kolonner. Du kan også uploade en fil uden at skrive kode fra dashboardet.

## Hvad et job kan

| `kind` | Hver række | Kolonner, jobbet læser |
| --- | --- | --- |
| `datavask` | Fritekstadresse → DAR-adgangsadresse med kategori A/B/C og score | `adresse`, eller `vej` + `husnr` med `postnr` og/eller `by` |
| `reverse` | Punkt → nærmeste adgangsadresse inden for 200 m | `x` + `y` |
| `bbr` | BBR-overblik for ejendommen: arealer, opførelsesår, anvendelse, materialer, varme | `bfe`, `adgangsadresse_id` eller en adresse |
| `dagi` | Region, kommune, sogn, retskreds, politikreds og opstillingskreds | `x` + `y`, `adgangsadresse_id` eller en adresse |
| `miljoe` | Miljøscreeningens niveau pr. stof, grund, olietanke og affaldsmængde | `bfe`, `adgangsadresse_id` eller en adresse |

Har en række flere af kolonnerne, bruges den mest præcise: et BFE-nummer før et adresse-id før en adresse, der skal vaskes. En adresse, der kun kan vaskes med kategori C, bruges aldrig til at slå en ejendom op — rækken havner i fejlfilen, så ingen får data om en forkert ejendom.

## Send listen

Listen kan sendes på tre måder til [POST /v1/jobs](https://grundfast.dk/docs/api/jobs-create). Én upload må fylde 10 MB.

**CSV**

```bash
curl "https://api.grundfast.dk/v1/jobs?kind=datavask&label=Kunder" \
  -H "Authorization: Bearer gf_live_..." \
  -H "Content-Type: text/csv" \
  --data-binary @kunder.csv
```

**Fil (multipart)**

```bash
curl https://api.grundfast.dk/v1/jobs \
  -H "Authorization: Bearer gf_live_..." \
  -F kind=bbr \
  -F 'columns={"bfe":"BFE-nr"}' \
  -F file=@portefoelje.csv
```

**JSON**

```bash
curl https://api.grundfast.dk/v1/jobs \
  -H "Authorization: Bearer gf_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "kind": "reverse", "rows": [{ "lon": 12.5696, "lat": 55.6757 }] }'
```

- CSV med komma, semikolon eller tabulator virker; skilletegnet læses af overskriftslinjen. Et UTF-8-BOM fra Excel ignoreres.
- NDJSON er ét JSON-objekt pr. linje. En fil i multipart kan også være et JSON-array af objekter.
- Hver række må have op til 100 kolonner. Alle dine kolonner kommer med tilbage i resultatet.

### En liste over uploadgrænsen

1. **Opret en kladde** — `POST /v1/jobs` med `start: false` (eller `?start=false`). Kladden behandles ikke.

2. **Tilføj rækker** — [POST /v1/jobs/:id/rows](https://grundfast.dk/docs/api/jobs-rows) så mange gange, det skal være. Rækkerne nummereres i den rækkefølge, de kommer.

3. **Start** — [POST /v1/jobs/:id/start](https://grundfast.dk/docs/api/jobs-start). En kladde, der ikke er startet efter et døgn, stoppes.

## Kolonner og koordinater

Uden `columns` genkendes rollerne på kolonnenavnene, fx `Adresse`, `Vejnavn`, `Hus nr.`, `Postnummer`, `By`, `lon`/`lat`, `x`/`y`, `BFE-nr` og `adgangsadresse_id`. Store og små bogstaver, mellemrum og tegnsætning er ligegyldige, men navnet skal passe helt: `kundenr` bliver ikke til et husnummer. Svaret viser i `job.columns`, hvad der blev valgt.

```json
{ "vej": "Gade", "husnr": "Nr", "postnr": "Postnr", "by": "By" }
```

Koordinater kan være WGS84 (`lon`/`lat`) eller UTM32 i meter (EPSG:25832). Systemet genkendes på værdierne — alt over 180 er meter — eller du sætter `srid` til `4326` eller `25832`. Decimalkomma som i `55,6761` er i orden.

## Fremdrift, pause og stop

Spørg [GET /v1/jobs/:id](https://grundfast.dk/docs/api/jobs-get) med få sekunders mellemrum. `processedRows` og `progress` vokser, efterhånden som bidder af listen bliver færdige. Status går fra `queued` til `running` og ender i `completed` — eller `failed`, hvis ikke en eneste række lykkedes.

- [Pause](https://grundfast.dk/docs/api/jobs-pause) lader bidden, der er i gang, blive færdig og stopper derefter. [Genoptag](https://grundfast.dk/docs/api/jobs-resume) fortsætter fra samme række.
- [Stop](https://grundfast.dk/docs/api/jobs-cancel) afslutter jobbet. Det, der allerede er gjort, kan stadig hentes; resten behandles aldrig og koster intet.
- Du kan have 5 åbne job ad gangen (kladder, i kø, kørende og på pause).
- Vil du ikke spørge, så abonnér på webhook-hændelsen `job.completed`, der sendes, når den sidste række er behandlet. Se [Webhooks](https://grundfast.dk/docs/webhooks).

> **Tempo** Job behandles i baggrunden i små bidder, på skift mellem kunderne, så ingen liste fylder databasen og kilderne for andre. Adresser med vej, husnummer og postnummer slås op direkte og går hurtigt; adresser, der skal vaskes som fritekst, tager op mod et sekund pr. række. En liste på 10.000 frie adresser kan derfor tage et par timer. Opslag i BBR, DAGI og miljøscreening går gennem de samme kilder som enkeltkaldene og følger deres tempo. Er en kilde nede, venter jobbet og prøver igen i stedet for at fylde fejlfilen.

## Resultat- og fejlfilen

[Resultatfilen](https://grundfast.dk/docs/api/jobs-results) har rækkerne med et svar, [fejlfilen](https://grundfast.dk/docs/api/jobs-errors) dem uden. Begge kan hentes, mens jobbet kører, og indeholder så det, der er færdigt.

- **CSV**: dine kolonner først, præcis som du sendte dem, og vores til højre med præfikset `gf_`. Filen starter med et UTF-8-BOM, og `?separator=semicolon` giver en fil, en dansk Excel åbner direkte.
- **NDJSON** (`?format=ndjson`): `{ "raekke", "input", "resultat" }` pr. linje i resultatfilen og `{ "raekke", "input", "fejl", "status" }` i fejlfilen. `raekke` er rækkens nummer i din liste, fra 1.
- `status` i fejlfilen er den HTTP-status, enkeltkaldet ville have givet: `404` for en adresse uden match, `422` for et for usikkert adressematch, `400` for en række uden de kolonner, jobbet læser.

| `kind` | Kolonner i resultatet |
| --- | --- |
| `datavask` | `kategori`, `score`, `antal_kandidater`, `adgangsadresse_id`, `betegnelse`, `vejnavn`, `husnr`, `postnr`, `postnrnavn`, `kommunekode`, `bfe_nummer`, `lon`, `lat` |
| `reverse` | `afstand_m`, `adgangsadresse_id`, `betegnelse`, `vejnavn`, `husnr`, `postnr`, `postnrnavn`, `kommunekode`, `bfe_nummer`, `lon`, `lat` |
| `bbr` | `adresse_kategori`, `adgangsadresse_id`, `betegnelse`, `bfe_nummer`, `kommunekode`, `matrikelnummer`, `ejerlav_kode`, `ejerlav_navn`, `antal_bygninger`, `samlet_bygningsareal_m2`, `samlet_boligareal_m2`, `samlet_erhvervsareal_m2`, `hovedbygning_anvendelse_kode`, `hovedbygning_anvendelse`, `opfoerelsesaar`, `om_tilbygningsaar`, `antal_etager`, `ydervaeg`, `tag`, `varmeinstallation`, `opvarmningsmiddel`, `asbest_registrering` |
| `dagi` | `adresse_kategori`, `adgangsadresse_id`, `betegnelse`, `lon`, `lat`, `region_kode`, `region_navn`, `kommune_kode`, `kommune_navn`, `sogn_kode`, `sogn_navn`, `retskreds_kode`, `retskreds_navn`, `politikreds_kode`, `politikreds_navn`, `opstillingskreds_kode`, `opstillingskreds_navn` |
| `miljoe` | `adresse_kategori`, `adgangsadresse_id`, `betegnelse`, `bfe_nummer`, `antal_bygninger`, `asbest`, `pcb`, `bly`, `pah`, `klorerede_paraffiner`, `tungmetaller`, `grund_niveau`, `jordforurening`, `olietanke`, `affald_ton`, `over_affaldsgraensen`, `mangler` |

Resultatet er et kompakt udtræk pr. række, så en liste på en million rækker ikke bliver en million fulde objekter. Har du brug for hele BBR-objektet eller hele screeningen, så slå de ejendomme, du er interesseret i, op med enkeltkaldet.

## Pris og grænser

- Én enhed pr. række, der lykkes — samme pris som enkeltkaldet. Rækker i fejlfilen koster intet.
- Uploaden tælles mod månedskvoten, så Free-planens loft også gælder for et job. Rækker, der ender i fejlfilen, og rækker i et stoppet job gives tilbage.
- Rækker pr. job følger planen: 250 på Free, 10.000 på Starter, 100.000 på Pro, 500.000 på Scale og 1.000.000 på Enterprise. En længere liste afvises med `402` og tæller ikke.
- Rækker og svar slettes 30 dage efter, at jobbet er færdigt eller stoppet. Jobbet står stadig på listen med `resultsAvailable: false`.

## Med SDK’et

```ts
import { readFile } from 'node:fs/promises';
import { GrundfastClient } from '@grundfast/sdk';

const gf = new GrundfastClient({ apiKey: process.env.GRUNDFAST_API_KEY });

const csv = await readFile('kunder.csv', 'utf8');
const job = await gf.createJobFromCsv('datavask', csv, { label: 'Kunder' });

const faerdig = await gf.waitForJob(job.id, {
  onProgress: (j) => console.log(Math.round(j.progress * 100), '%'),
});
console.log(faerdig.succeeded, 'fundet,', faerdig.failed, 'i fejlfilen');

const resultat = await gf.downloadJobResults(job.id, { separator: 'semicolon' });
const fejl = await gf.downloadJobErrors(job.id, { format: 'ndjson' });
```

- Job: se https://grundfast.dk/docs/api
