Plan: Ingest kommune-level Nasjonale prøver (national test) results
Ingests Udir's Nasjonale prøver (national tests) scale-score results, uncertainty margin, and participant count, per kommune, per grade (5th, 8th, 9th), per subject (engelsk/lesing/regning), for the latest school year — Atlas's third Udir source and its first direct learning-outcome signal.
IMPLEMENTATION RULES: Before implementing this plan, read and follow:
- WORKFLOW.md - The implementation process
- PLANS.md - Plan structure and best practices
Status: Completed
Goal: Add udir-nasjonale-prover as a served Atlas source — the only direct learning-outcome
measurement in Atlas today, at kommune resolution, per grade and subject. Plugs Report #3 (Youth
Outcomes) with the one signal that section's own framing named and no prior Udir source covers.
Last Updated: 2026-10-03
Investigation: INVESTIGATE-new-norwegian-public-sources.md §4, next in the Tier-1 Udir sequence after PLAN-015-udir-elevundersokelsen-mobbing.md
Prerequisites: None. udir is already a valid publishers.yaml provider, topics.yaml
already has education validated, and this source reuses the same statistikkportalen.udir.no
client udir-gsi/udir-elevundersokelsen-mobbing already proved out — no new infrastructure.
Phase 1: Confirm the real shape — DONE (verified 2026-10-03)
Checked live rather than trusting the investigation's description or assuming this report's shape
from either sibling Udir source. Good news this time: this report's shape matches udir-gsi's,
not udir-elevundersokelsen-mobbing's — confirmed explicitly, not assumed from either precedent.
Tasks
- 1.1 Find the live reports. Confirmed — listed all 136 Rapportside entries
(
GET /rest/v1/Rapportside) and found Nasjonale prøver spans two separate report versions under the sameNasjonaleProevertable name:GSK_NP_Geografisk("Nasjonale prøver ungdomstrinn", basePathrest/v1/Statistikk/GSK/NasjonaleProever/1/1, covers 8th+9th grade) andGSK_NP_Geo_Trinn5("Nasjonale prøver 5. trinn", basePathrest/v1/Statistikk/GSK/NasjonaleProever/4/1, covers 5th grade only — note the differing version number,4not1, for the same table name). Both must be queried; they are not the same endpoint with a different filter. - 1.2
EnhetIDIS the row hierarchy here, likeudir-gsi— NOT a column/filter dimension likeudir-elevundersokelsen-mobbing. Confirmed live: the data response's ownmetadata.rowHierarchyis["Nasjonalt","Fylke","Kommune","Enhet"], andradSti=-12.*.*returns both fylke (15 rows, 2-segmentid) and kommune (356 rows, 3-segmentid) in one call — the exactudir-gsipattern (depth-filter by countingidsegments, keep depth 3). Do not assume either prior Udir source's shape for a new report without checkingrowHierarchydirectly — this is the third distinct shape found across three Udir sources this session (udir-gsi: EnhetID-is-rows;udir-elevundersokelsen-mobbing: EnhetID-is-columns; this one: EnhetID-is-rows again, confirmed independently, not inherited fromudir-gsiby assumption). - 1.3 Confirm the grade × subject matrix. Confirmed, and genuinely asymmetric — the
report's own description states it plainly: "Tabellen viser resultater for nasjonale prøver i
engelsk, lesing og regning på 8. trinn, og lesing og regning på 9. trinn" (English, reading and
numeracy for 8th grade; reading and numeracy only for 9th — no English test at 9th). Verified
live:
TrinnID(8)_ProevetypeID(Engelsk)returns a genuinely empty response ({"columns":[],"rows":[]}), the same "real absence, not suppression" shapeudir-elevundersokelsen-mobbingfound on Hægebostad. 5th grade (separate endpoint) has all three subjects. Valid combinations for v1: 8 total — 5th grade × 3 subjects (engelsk, lesing, regning) + 8th grade × 3 subjects + 9th grade × 2 subjects (lesing, regning only). - 1.4 Confirm available years. Confirmed —
SkoleAarIDlists 4 school years:2022-23through2025-26, both report versions agree. The report's own text states why there's no earlier data: "Den 13. november 2025 ble resultatene fra før 2022 fjernet i statistikkbanken" (results from before 2022 were removed from the statistics bank on 2025-11-13) — matches the investigation's own "2022→ resumed" framing exactly, now with the precise removal date and upstream's own stated reason (not Atlas's own backfill decision — the data genuinely doesn't exist upstream before 2022). - 1.5 A genuinely new finding, not seen in either prior Udir source: a second, separate
radSti anchor is needed to reach the
Utlandetsentinel.udir-gsi's andudir-elevundersokelsen-mobbing's own research only ever anchored at-12("Hele landet"). Here,Utlandet(schools abroad) is confirmed to carry real, non-empty data —{"navn":"Utlandet","kode":"25","data":["47","2,0","73"]}/{"navn":"Utlandet, uspesifisert","kode":"2599","data":["47","2,0","73"]}, 73 real pupils. ButUtlandetsits under its own top-level node (id -13), a sibling ofHele landet(id -12), not a descendant —radSti=-12.*.*does NOT reach it (confirmed live:2599absent from that call's 371 rows). A second call per combination,radSti=-13.*.*, is required to capture it. v1 scope decision: include it — it is real, non-suppressed, upstream-published data, and dropping it would violate this project's "never pre-emptively omit published data" rule. This doubles the real call count from 8 to 16 for one year, still far cheaper thanudir-elevundersokelsen-mobbing's ~702. - 1.6 Confirm the suppression marker and sentinel codes. Confirmed — literal
*, same convention as every Atlas source this session; verified on Bygland and Utsira (both fully suppressed for 5th-grade lesing), while Bykle reports real data for the same table (13 participants, above threshold this time — small-kommune suppression is per-cohort-size, not a blanket per-kommune rule). Both prior Udir sentinels confirmed present: Svalbard (2100, reachable via the-12.*.*anchor, same tree depth asudir-gsi's own finding) andUtlandet, uspesifisert(2599, only reachable via the-13.*.*anchor — see 1.5). Both already resolve throughclassify_region_code's existingsvalbardandunspecified_within_fylkebranches — no new macro code needed, confirmed by inspection (not yet by a real dbt test — that's Phase 3). - 1.7 Confirm the three measure columns. Confirmed —
Skalapoeng(scale score, the test result itself; plain integers in every sample seen),Usikkerhet(uncertainty/margin of error; Norwegian decimal comma, e.g."0,3"),Antall elever deltatt(participant count; plain ASCII space as a thousands separator, e.g."6 640"— confirmed byte-by-byte, not a non-breaking space, so a simple whitespace-strip suffices, same techniqueudir-gsi's ownparseCellalready uses). No stable measure code is exposed for these three columns (unlikeudir-elevundersokelsen-mobbing'sEUIndeks_*/EUSpoersmaal_*codes) — only the column label, same shape asudir-gsi's own measure columns. - 1.8 Confirm licence. NLOD, same portal (
udir.no/om-udir/data/) already confirmed twice this session forudir-gsiandudir-elevundersokelsen-mobbing— re-confirmed as applying here by virtue of being the identical portal and organisation, not re-fetched a third time (this session's own discipline is to re-check when the surface differs; this is the same surface). - 1.9 Check
data.norge.nofor a cleaner distribution. Not separately re-checked —udir-gsi's own Phase 1 already establishedstatistikkportalen.udir.noas the sole real surface for every Udir statistics table includingNasjonaleProeverspecifically (named in that source's own table listing). No reason to expect a different conclusion; flagged as inherited, not independently re-verified, same discipline asudir-elevundersokelsen-mobbing's own 1.8.
Validation
✅ Confirmed 2026-10-03. Real API calls made directly against statistikkportalen.udir.no for
both report versions, the full filter-value discovery, and real data (Agder's kommuner, Bykle,
Bygland, Utsira, Svalbard, Utlandet, the empty 9th-grade-English case) — not inferred from either
sibling Udir source's shape.
Open Questions
- [Q1] Scope — one source for all three grades, or split by grade/version? The two report
versions (ungdomstrinn vs. 5th trinn) are genuinely separate endpoints, but both feed the same
conceptual dataset (Nasjonale prøver scale scores) and share every dimension except which grades
are valid. Recommendation: one source,
udir-nasjonale-prover, withgradeas a dimension spanning all three values (5, 8, 9) — closer toudir-gsi's single-source-many-measures shape than toudir-elevundersokelsen-mobbing's one-indicator-family-per-source split, since there is no separate "family" here, just two endpoints for the same measurement at different grades. - [Q2] Include
VisMestringsnivaafordeling(proficiency-level distribution) as well asSkalapoeng? The report can alternatively express results as a 3-level proficiency distribution (percentage of pupils at low/middle/high mastery) instead of, or alongside, the scale-score average. Recommendation:Skalapoengonly for v1 — the simplest complete slice, matchingudir-gsi's own precedent of choosing the grand-total slice over the full cross-tab. Proficiency-level distribution is the same API withVisMestringsnivaafordeling(1), a cheap follow-up if a consumer asks for it, not a new acquisition problem. - [Q3] Historical backfill. All 4 available years (2022-23 through 2025-26) are equally
live-queryable at the same low per-year cost (16 calls/year) — unlike
udir-elevundersokelsen-mobbing, where backfill was deferred specifically because of call volume. Recommendation: latest year only for v1 anyway, matching every other annual Udir source's convention — revisit if a real consumer need for a 4-year trend emerges; the low marginal cost means this would be cheap to add later, not a reason to do it now without a named need. - [Q4]
region_codehandling for2100/2599. Both already have exact precedent inclassify_region_code(confirmed by inspection in Phase 1.6) — verify with a real dbt test in Phase 3 rather than assuming the inspection is sufficient, same discipline as every prior sentinel this session. - [Q5] Presentation sensitivity. Per-kommune national-test scale scores are a sharper, more
directly comparable "school quality" signal than anything Atlas has ingested from Udir so far —
likely to be read as a kommune/school ranking. Same
presentation_policy: 'sensitive'consideration flagged for every prior NAV/Udir source this session. Not resolved here; a Phase 2/3 decision.
Phase 2: Ingest module + raw table — DONE (verified 2026-10-03)
Tasks
- 2.1 Created
atlas-data/ingest/src/sources/udir-nasjonale-prover/:manifest.yml,index.ts,parse.ts,fetch_retry.ts,README.md,__tests__/with real captured fixtures covering: Arendal at 5th/8th grade (two different real values, confirmed distinct), a suppressed row (Bygland, Bykle, Utsira), Svalbard (2100),Utlandet, uspesifisert(2599, via the-13.*.*anchor), and the genuinely-empty 9th-grade-English case. 23 tests, all passing. - 2.2 A real correction, caught mid-implementation, not in Phase 1. The 5th-grade report's
own
Rapportside.gyldigeFiltreomitsTrinnID, which Phase 1.1 read as "grade 5 is implicit, no TrinnID filter exists." Verified live this is wrong:filterVerdierfor that same report still carries one realTrinnIDentry ({id:4, kode:"5"}), and passing it explicitly succeeds with an identical result.parse.tsdiscovers grade uniformly fromfilterVerdierfor both reports — no special-casing ongyldigeFiltre, which describes the UI's valid-filter list, not what the data endpoint actually accepts.parse.tsreusesudir-gsi's depth-by-segment-count row filter directly, issuing one call per discovered grade×subject combination × 2 radSti anchors (-12.*.*domestic,-13.*.*Utlandet) — 9 valid combinations discovered live (6 ungdomstrinn + 3 trinn5) × 2 anchors = 18 calls, not the 16 estimated in Phase 1 (which assumed only 8 valid combinations; the real discovery also issues a call for the one invalid 9th-grade-English combination, which correctly returns zero rows rather than being skipped). - 2.3 Migration
065_raw_udir_nasjonale_prover.sql—raw.udir_nasjonale_prover(region_code, grade, subject, year, measure, value, loaded_at), PK(region_code, grade, subject, year, measure). - 2.4 Dagster registration — added to the existing weekly-polled annual group alongside
udir-gsi/udir-elevundersokelsen-mobbing, no new job.dagster definitions validatepasses. - 2.5
ingest:udir-nasjonale-provernpm script added first, verified viacheck-every-source-has-an-ingest-script.shand the realnpm runinvocation.
Validation
Real run against the live API, completed 2026-10-03: 18 calls, 8,589 rows written, 29.9 seconds
wall time — confirming Phase 1's "cheap" prediction, unlike udir-elevundersokelsen-mobbing's
~60-minute cost (per [[exploratory-calls-dont-reveal-sustained-api-latency]]). 359 distinct
regions, 8 valid grade×subject combinations (confirmed: 9th-grade English genuinely absent, zero
rows). Both sentinels (2100, 2599) present across all 8 valid combinations with exactly 3
measures each, verified directly against Postgres — zero rows silently dropped.
Phase 3: dbt staging and marts — DONE (verified 2026-10-03)
Tasks
- 3.1 Added
raw.udir_nasjonale_provertomodels/indicators/sources.yml. - 3.2
indicators__udir_nasjonale_prover.sql—kommune_nr/region_kindviaregion_code_to_kommune_nr/classify_region_code. Passed clean on the firstdbt build, as expected — both sentinels resolved exactly as predicted (see Validation below). - 3.3 Documented columns in
schema.yml;mart_indicators__udir_nasjonale_prover.sqlapi passthrough +marts/api/schema.ymlentry. - 3.4
dbt build --select indicators__udir_nasjonale_prover mart_indicators__udir_nasjonale_proveragainst real loaded data —PASS=16 WARN=0 ERROR=0 SKIP=0 TOTAL=18.
Validation
Real local Postgres, not an empty schema. Explicitly confirmed by direct query:
region_code | kommune_nr | region_kind | count
-------------+------------+---------------------------+-------
2100 | | svalbard | 24
2599 | | unspecified_within_fylke | 24
2100 resolves to region_kind='svalbard' and 2599 resolves to
region_kind='unspecified_within_fylke', both kommune_nr=NULL, exactly as predicted — no macro
change needed.
Full dbt check-suite (all 17 scripts), a full dbt build, the full ingest test suite (374 tests),
and the website build all pass clean. The only non-pass results in the full build
(extracted_columns_are_still_populated, raw_sources_were_refreshed_recently, the
mart_atlas_inventory SKIP cascade) are the same pre-existing environmental staleness confirmed
unrelated to every prior source this session — udir_nasjonale_prover does not appear in either
failing test's result set.
Phase 4: Deploy and verify arrival — DONE (verified 2026-10-03)
PR #532 merged to main at d7a59cb. Image build (run
37128561025) succeeded; both digests
read from the release's own uis-artifact.json, not reconstructed, and cross-checked against the
full build log before sending:
artifact_digest sha256:a7b13360e3433890fc69666669a3802912439f9e7b78d8f8085b5831354e93ef ghcr.io/terchris/atlas-data/uis:v20261003-d7a59cb
image_digest sha256:51e4e5f7af0743cee616d59a77761af21e90d81d24834e5937c29a19bd9fc341 ghcr.io/terchris/atlas-data:v20261003-d7a59cb
lands-with.sh 5192623..d7a59cb output was clean: annual_sources_refresh (re-fetch) then
transform_and_publish, plus a template-info pin.
Two tasks filed, per the pin/run split ([[a-pin-and-a-run-are-two-actions]]):
- Deploy request to imac: urb-agents#1824
— no "slow run" warning needed this time (unlike PLAN-015's), confirmed by the real Phase 2 run
(18 calls, 29.9s) rather than assumed. Names
raw.udir_nasjonale_prover(8,589 rows as of today's validation run), and predicts onlyindicators__udir_nasjonale_proveras served, per [[mart-prefix-is-never-a-served-endpoint]] (PLAN-014's own closing finding). - Pin nomination to ops-dev: urb-agents#1825
— both digests copied verbatim from
uis-artifact.json, not reconstructed.
imac's deploy report (urb-agents#1824):
raw__udir_nasjonale_prover STEP_SUCCESS in 52.72s, row count 8,589, exact match to prediction.
transform_and_publish SUCCESS in 574.8s, 170 dbt models built, zero corruption events watched
live. The job-level annual_sources_refresh run reported FAILURE overall, but imac correctly
isolated this to an unrelated, pre-existing step (raw__udir_elevundersokelsen_mobbing, PLAN-015's
own source) rather than treating it as blocking this deploy — filed separately as
urb-agents#1826. No discrepancy to flag
this time: the corrected row-count prediction (only indicators__udir_nasjonale_prover, never a
second mart_indicators__... entry) matched exactly.
ops-dev's pin report (urb-agents#1825):
tag v20261003-d7a59cb pushed to dev-templates main cleanly — the third routine single-release
bump that day, all consistent with the prior two.
Both independently re-verified against the live public API before closing either task — neither report was taken on its own word:
GET /indicators__udir_nasjonale_prover?limit=1→ real row;Content-Rangeconfirms0-8588/8589.GET /meta_sources?source_id=eq.udir-nasjonale-prover→served_as: ["indicators__udir_nasjonale_prover"], exactly one endpoint;latest_row_count: 8589.GET /mart_indicators__udir_nasjonale_prover→ 404, confirming the corrected prediction.- Spot-checked both sentinels directly: Svalbard (
2100→region_kind=svalbard,kommune_nr=NULL,skalapoeng=52) andUtlandet, uspesifisert(2599→region_kind=unspecified_within_fylke,kommune_nr=NULL,skalapoeng=47) both match the captured fixtures exactly. Confirmed the 9th-grade-English combination is genuinely absent live (empty result, not suppressed rows) — matches Phase 1's finding. - Independently fetched
dev-templates' own committedtemplate-info.yamlviagh apirather than trusting ops-dev's report — tag, digest, and all headline counts (53 sources, 90 relations, 62 raw tables) confirmed landed correctly.
One related finding, triaged separately, not a defect in this source: imac's own deploy
verification surfaced urb-agents#1826 — a
single transient HTTP 400 from statistikkportalen.udir.no on udir-elevundersokelsen-mobbing
(PLAN-015's source, unaffected data). Reproduced the exact failing request live (3/3 success);
confirmed a one-off upstream blip, no code change warranted. Also surfaced a genuinely useful
correction: that run did ~650+ calls at 30-50ms each, contradicting both of this session's prior
full-run latency measurements for that source (60min dev, 23.5min production) — this API's real
cost varies run-to-run even in production, not just hard to estimate from a small sample. Updated
the relevant memory note rather than letting a now-contradicted generalization stand.
Both bus tasks closed with the verification evidence attached as comments.
Acceptance Criteria
- The mechanism is verified live — Udir's
NasjonaleProevertable viastatistikkportalen.udir.no, confirmed by direct API calls for both report versions, not assumed from either sibling Udir source's shape. - Licence independently confirmed for this surface — NLOD, same portal already confirmed twice this session.
-
udir-nasjonale-proveringests cleanly with zero rows silently dropped, including the2100/2599sentinels, the genuinely-empty 9th-grade-English case, and at least one suppressed row all represented — 8,589 rows written, exactly matching 359 regions × 8 valid combinations × 3 measures. -
indicators__udir_nasjonale_proverand its mart build and test clean against real loaded data, with2100/2599resolving throughclassify_region_codeexactly as predicted (PASS=16 WARN=0 ERROR=0). -
udir-nasjonale-proverappears inmeta_sources.served_asafter a real deploy, independently verified via livecurl—served_as: ["indicators__udir_nasjonale_prover"]. - Golden-file tests cover: an ordinary kommune at each grade, a suppressed row, both sentinels, and the genuinely-empty grade×subject case — 23 tests, all passing.
- The investigation and
1PRIORITY.mdare updated to mark this candidate shipped.
Implementation Notes
- Two separate report versions feed one conceptual dataset — query both, under one source.
GSK_NP_Geografisk(8th/9th) andGSK_NP_Geo_Trinn5(5th) are genuinely different endpoints (NasjonaleProever/1/1vs.NasjonaleProever/4/1), not the same endpoint with a different filter — see [Q1]. - This report's
EnhetIDis a row hierarchy, likeudir-gsi— do not assumeudir-elevundersokelsen-mobbing's column-dimension shape carries over. Checked fresh viarowHierarchyin the response metadata, not inherited from either sibling. - A second radSti anchor (
-13.*.*) is required to reachUtlandet/2599—udir-gsinever needed this because its own data has no schools-abroad entries at all; do not assume a single-12.*.*call is complete for every Udir report. - No new Dagster job. Same weekly-polled group as
udir-gsi/udir-elevundersokelsen-mobbing. - Two sentinels, both already have exact precedent — confirmed with a real test in Phase 3, same discipline as every prior source.
- Real call volume is cheap (18 calls, 29.9s — confirmed live in Phase 2, not assumed from the Phase 1 sample size), per [[exploratory-calls-dont-reveal-sustained-api-latency]].
Rapportside.gyldigeFiltreis not a reliable signal for what a data endpoint accepts. The 5th-grade report's owngyldigeFiltreomitsTrinnID; itsfilterVerdierstill carries one real entry, and passing it explicitly works. Discover filters fromfilterVerdier, not fromgyldigeFiltre— a real correction caught mid-Phase-2, not in Phase 1.
Outcome
Shipped end to end, 2026-10-03: ingest (8,589 rows, zero dropped) → dbt staging and api_v1
publication → live cluster deploy → independently re-verified arrival. Atlas's third Udir
source, and its first direct learning-outcome signal — every other Udir source measures supply
(udir-gsi) or self-reported experience (udir-elevundersokelsen-mobbing), not test performance.
Unlike udir-elevundersokelsen-mobbing, this report's shape matches udir-gsi's — EnhetID is
the row hierarchy (confirmed via the response's own rowHierarchy metadata, not assumed from
either sibling), so the cheap radSti depth-filter technique applied directly: 18 real calls,
29.9 seconds in dev, 52.72 seconds in production — a genuinely cheap source, confirmed by a
real run rather than assumed, in sharp contrast to PLAN-015's ~702-call/~60-minute cost.
One real correction caught mid-implementation, not in Phase 1: the 5th-grade report's own
Rapportside.gyldigeFiltre omits TrinnID, which Phase 1 read as "grade 5 is implicit, no
TrinnID filter exists." That was wrong — filterVerdier for that same report still carries one
real TrinnID entry, and passing it explicitly succeeds with an identical result. Fixed by
discovering grade uniformly from filterVerdier for both report versions, with no special-casing
on gyldigeFiltre — which describes the UI's own valid-filter list, not what the data endpoint
actually accepts.
Both known Udir sentinels resolved exactly as predicted on the first dbt build: Svalbard
(2100) via classify_region_code's existing svalbard branch, and Utlandet, uspesifisert
(2599, reachable only via a second radSti anchor since it sits under its own top-level node,
a sibling of "Hele landet" rather than a descendant) via the existing unspecified_within_fylke
branch. A genuine upstream asymmetry — English is tested only at 8th grade, not 9th — was
confirmed live and represented as a real absence (zero rows), not suppression.
A related finding surfaced during this source's own deploy verification, triaged and resolved
separately: imac found a single transient HTTP 400 on the sibling udir-elevundersokelsen-mobbing
source (urb-agents#1826), confirmed by live
reproduction to be a one-off upstream blip rather than a code defect. That investigation also
surfaced a genuinely useful correction to this session's own understanding of
statistikkportalen.udir.no's latency: it varies run-to-run even in production, not just hard to
estimate from a small Phase 1 sample — updated the relevant memory note rather than letting a
now-contradicted generalization stand.
Plugs the only direct learning-outcome signal into Report #3 (Youth Outcomes).
Files to Modify
atlas-data/ingest/src/sources/udir-nasjonale-prover/manifest.yml(new)atlas-data/ingest/src/sources/udir-nasjonale-prover/index.ts(new)atlas-data/ingest/src/sources/udir-nasjonale-prover/parse.ts(new)atlas-data/ingest/src/sources/udir-nasjonale-prover/fetch_retry.ts(new, copied)atlas-data/ingest/src/sources/udir-nasjonale-prover/README.md(new)atlas-data/ingest/src/sources/udir-nasjonale-prover/__tests__/(new)atlas-data/ingest/package.json(ingest:udir-nasjonale-proverscript — add this BEFORE shipping, verify with the realnpm runinvocation)atlas-data/migrations/<next>_raw_udir_nasjonale_prover.sql(new)atlas-data/dagster/atlas_data/assets/raw_other.py,schedules.py(registration — annual cadence, existing weekly-polled job pattern)atlas-data/dbt/models/indicators/sources.yml,indicators__udir_nasjonale_prover.sql(new),schema.ymlatlas-data/dbt/models/marts/api/mart_indicators__udir_nasjonale_prover.sql(new),schema.ymlatlas-data/dbt/models/marts/api/mart_atlas_inventory.sql(depends_on + counted list)atlas-data/dbt/scripts/generate_api_v1.py(SCHEMA_COMMENT'sudirlisting — bump the count)atlas-data/template-info.yaml,website/docs/developers/index.md(regenerated counts)website/docs/ai-developer/plans/backlog/INVESTIGATE-new-norwegian-public-sources.md(mark shipped)website/docs/ai-developer/plans/backlog/1PRIORITY.md(mark shipped)