Plan: Ingest kommune-level Elevundersøkelsen bullying ("mobbing") indicators
Ingests Udir's Elevundersøkelsen (pupil survey) bullying indicator and its three underlying
questions, per kommune per grade (7th and 10th) per school year, as Atlas's second Udir source —
a sharper, annual complement to the existing fhi-mobbing 3-year-rolling aggregate.
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-elevundersokelsen-mobbing as a served Atlas source — an annual, per-grade
bullying-prevalence signal at kommune resolution, sharper than fhi-mobbing's 3-year-rolling
7th+10th aggregate. Plugs Report #3 (Youth Outcomes) and strengthens Report #10
(School-Capacity Forecast)'s learning-environment axis.
Last Updated: 2026-10-03
Investigation: INVESTIGATE-new-norwegian-public-sources.md §4 ([Q10]-[Q13]), next in the Tier-1 sequence after PLAN-014-nav-helt-ledige.md
Prerequisites: None. udir is already a valid publishers.yaml provider (landed with
PLAN-010-udir-gsi.md), topics.yaml already has the relevant categories validated, and this
source reuses the exact same statistikkportalen.udir.no API client shape udir-gsi already
proved out — no new infrastructure, but genuinely new response-shape handling (see Phase 1).
Phase 1: Confirm the real shape — DONE (verified 2026-10-03)
Checked live rather than trusting the investigation's description (last touched 2026-05, well
before udir-gsi found the real API). This source's real shape is structurally different from
every prior source this session — not a NAV-Excel pivot, not an SSB PxWebAPI call, and not even
the same response shape as its own sibling udir-gsi despite sharing one client.
Tasks
- 1.1 Confirm the real API and find the live report. Confirmed — same system
udir-gsialready found,statistikkportalen.udir.no/api/rapportering. Listed every Rapportside (GET /rest/v1/Rapportside, 136 report pages total) and found the live bullying report has migrated schemas since the investigation's own framing: the classicElevundersoekelsenGtable's own Rapportside (GSK_EU_mobbing) is stale — itsfilterDefaultVerdier.TidIDis frozen at[201901, 202001, 202101]. The live, current table isEUG(GSK_EUG_mobbing, endpointrest/v1/Statistikk/GSK/EUG/5/5), whose own default year is202512("2025-26") — confirmed by fetching both Rapportside definitions directly and comparing, not assumed from naming similarity. Don't reuse theElevundersoekelsenGtable name anywhere in this source — it is the retired predecessor. - 1.2 Confirm available years. Confirmed —
filterVerdieronTidIDreturns five school years:2025-26, 2024-25, 2023-24, 2022-23, 2021-22(ids202512, 202412, 202312, 202212, 202112). All five are live-queryable today, not an archive-vs-live split like NAV's tables. - 1.3 A fundamentally different response shape than
udir-gsi's — read this before writing a parser.udir-gsi'sEnhetID(geography) is the row hierarchy, filtered to kommune depth byradSti. For this report,EnhetIDis a column dimension, not a row. The row hierarchy isIndikator/Spørsmål(max 2 levels — confirmed by a 400 error when a 3-levelradStiwas tried:"For dyp radsti: for denne rapporten støttes maksimalt 2 nivåer"). Geography has no wildcard-and-filter-by-depth mechanism the wayudir-gsi's did:- A wildcarded
EnhetID(e.g.-12.*, "all of Norway, one level down") returns the entire national column tree in one response — nestedcolumns[]arrays down through landet → fylke → kommune → individual school, each carrying acolumnCount(how many leaf columns live under it). Confirmed live: a real response for 2025-26 nests all 17 fylker, then every kommune under each, then every individual school under each kommune. - A specific, non-wildcarded
EnhetID(e.g.-720, Arendal's internal id) collapses the response to exactly one column, labelled "Alle skoler" — confirmed this is the kommune's own aggregate value, not a drill into its schools, by comparing against the all-of-Norway response's column structure for the same kommune. - [Q1] resolved 2026-10-03, at the start of Phase 2 — per-kommune calls, deliberately, not the
bulk decode. The wildcard tree's leaf columns were never confirmed to contain a distinct
per-kommune aggregate separate from its individual schools — decoding it without that
guarantee risks silently extracting a specific school's number as if it were the kommune's,
exactly the defect class this project's "verified correct, not merely clever" discipline (see
udir-gsi's own README) exists to avoid. The per-entity-collapse technique is unambiguous ("Alle skoler"label, cross-checked against the national tree for the same kommune) and costs one HTTP call per region node per grade —filterVerdier's ownEnhetIDlist has 351 nivaa-3 nodes (349 real kommuner + Svalbard's2100+Utlandet, uspesifisert's2599— fewer than the ~357-359 seen in every prior kommune-grain source, because a kommune with zero schools reporting into Elevundersøkelsen that year is simply absent from the tree, not present-and-suppressed; confirm this distinction with a real row-count check in Phase 2) × 2 grades = ~702 calls for one year, plus 2 setup calls. [Q2]'s scope follows from this: latest year only for v1 (all 5 years would be ~3,510 calls — defer backfill, same as every annual Udir table's own convention). A deliberate pacing delay between calls is warranted given the API's own "not intended for external use" disclaimer — courtesy, not a documented rate limit.
- A wildcarded
- 1.4 Confirm the grade ("Trinn") filter semantics. Confirmed, and this corrects an
assumption carried from
udir-gsi.udir-gsi'sTrinnID(-10)/KommunalitetID(-10)sentinel meant "alle" (all grades/ownership types summed). For this report,-10is NOT a valid "alle" sentinel at all —TrinnID's realfilterVerdieronly lists ids4–9(grades 5–10), no "alle" option. PassingTrinnID(-10)did not error; it silently returned the last value in the report's own default list ([6, 9]→ grade 10) without flagging anything wrong — caught only by cross-checking the result against an explicitTrinnID(9)call and finding identical values. This is exactly the kind of silent-wrong-default trap this project's "verify, don't assume" discipline exists for.TrinnID(6,9)(comma-joined, guessing at multi-value syntax) returns a genuine 400 ("Ugyldige tegn i filter") — there is no single call for "both grades at once"; v1 must issue one call per grade. Matchesfhi-mobbing's own established 7th+10th-grade axis, so this is also the natural v1 scope, not an arbitrary restriction. - 1.5 Confirm the suppression marker. Confirmed — literal
"*", same convention as every other Atlas source this session. Verified on Bykle (Agder, Norway's smallest kommune by population): its 10th-grade 2025-26 "Mobbing på skolen" indicator and one of its three questions are suppressed ("*"), while the other two questions report real"0,0"values in the same response — suppression is per-row, not per-kommune, matchingudir-gsi's and every NAV source's own per-cell suppression. - 1.6 A genuinely new sentinel, not seen in
udir-gsi: Norwegian schools abroad. The national column tree's top level has two siblings,"Hele landet"(id-12) and"Utlandet"(id-13, kode"U") — confirmed live, Elevundersøkelsen is answered by Norwegian schools operating abroad (e.g. "Den norske skole - Costa Blanca"). UnderUtlandet's own fylke-equivalent node (kode"25"— ⚠️ the retired pre-2024 Finnmark fylke code, a real collision worth flagging even though fylke-level codes are out of scope for this kommune-grain source) sits exactly one kommune-equivalent node:"Utlandet, uspesifisert", kode2599— which matchesclassify_region_code's existing^\d{2}99$unspecified_within_fylkebranch exactly, confirmed by inspection, no new macro branch needed.udir-gsi's own README andparse.tsnever mention this sentinel; not assumed to carry over, checked fresh for this source. - 1.7 Confirm licence. NLOD, confirmed directly on Udir's own data portal
(
udir.no/om-udir/data/, which this source shares withudir-gsi— same portal, same organisation, re-fetched and read for this source rather than assumed to transfer): "lisens for offentlig data (NLOD)." - 1.8 Check
data.norge.nofor a cleaner distribution. Not separately re-checked this round —udir-gsi's own Phase 1 already establishedstatistikkportalen.udir.noas the real, current, sole surface for every Udir statistics table including Elevundersøkelsen (confirmed by listing every table under schemaGSK, which includesEUG/ElevundersoekelsenGalongsideGSI). No reason to expect a different conclusion for this specific table; flagged as inherited, not independently re-verified, per this session's own discipline about not assuming licence/surface findings transfer silently between sibling sources — if this matters, re-check before shipping.
Validation
✅ Confirmed 2026-10-03. Real API calls made directly against statistikkportalen.udir.no for the
Rapportside definition, filter values, and real data (Arendal and Bykle, both grades, latest year)
— not inferred from udir-gsi's shape or the investigation's five-month-old description.
Open Questions
- [Q1] Bulk-decode vs per-kommune calls — RESOLVED 2026-10-03, per-kommune calls. The
wildcard-and-decode route was never confirmed to carry a distinct per-kommune aggregate leaf
separate from its individual schools — decoding it on that unverified assumption risks silently
extracting one school's figure as if it were the kommune's. The per-entity-collapse technique
(
EnhetIDpinned to one specific id → response collapses to one column labelled"Alle skoler") is unambiguous, already cross-checked against the national tree for the same kommune, and costs one call per region node per grade:filterVerdier's ownEnhetIDlist has 351 nivaa-3 nodes (349 kommuner + Svalbard's2100+Utlandet, uspesifisert's2599) × 2 grades = ~702 calls for one year — far more than any prior source. At Phase 1's exploratory pace this looked cheap (tens of ms per call); a real full run (Phase 2) found that assumption wrong — only the first ~22 calls stay fast, every call after settles into a steady ~3.5-6s each, for ~60 minutes total wall time. Correctness over call count was still the right call — the alternative (bulk-decode) risked a silent misparse, not just a slower run — but the real cost of this decision turned out to be wall-clock minutes, not request count. - [Q2] Historical backfill — RESOLVED 2026-10-03, latest year only for v1. Follows directly from [Q1]: per-kommune calls make every extra year ~702 more requests (all 5 years would be ~3,510). Defer backfill, same convention as every other annual Udir table — revisit if a real consumer need for historical Elevundersøkelsen trend data emerges.
- [Q3] Scope — mobbing only, or fold in trivsel/other Elevundersøkelsen indicator families too?
Elevundersøkelsen covers many indicator families (
GSK_EUG_mobbing,GSK_EUG_indikator/ learning-environment,GSK_EUG_tema, and more) under one survey, structurally closer to Bufdir's multi-indicator monitors than to NAV's one-indicator-per-source tables. Recommendation:udir-elevundersokelsen-mobbingv1 ingests only the bullying indicator family (direct complement tofhi-mobbing, the gap this plan is named for) — matching Bufdir's own precedent of one source folder per indicator family (bufdir-barnefattigdom,bufdir-barnevern), not one mega-source. A futureudir-elevundersokelsen-trivselor similar would reuse this exact client. - [Q4] GSK (grunnskole) only, or also VGO (videregående)?
ElevundersoekelsenV/EUG-VGO exists under schemaVGOwith the identical mechanism. Recommendation: GSK only for v1, matchingudir-gsi's own grunnskole-only scope — VGO as a natural, cheap follow-up reusing this client, not folded in here. - [Q5]
region_codehandling forUtlandet/2599. See Phase 1.6 — this falls throughclassify_region_code's existingunspecified_within_fylkebranch cleanly by inspection; verify with a real dbt test during Phase 3 rather than assuming the inspection is sufficient, same discipline as every prior sentinel this session. - [Q6] Presentation sensitivity. Per-kommune, per-school-year bullying share for minors is
sensitive in the same way
fhi-mobbingalready is — samepresentation_policy: 'sensitive'consideration as every prior NAV-adjacent source. Not resolved here; a Phase 2/3 decision.
Phase 2: Ingest module + raw table — DONE (verified 2026-10-03)
Tasks
- 2.1 [Q1] resolved — per-kommune calls, not bulk-decode. See Phase 1.3/[Q1]. ~702 calls for the latest year (351 region nodes × 2 grades), with a deliberate pacing delay between calls.
- 2.2 Created
atlas-data/ingest/src/sources/udir-elevundersokelsen-mobbing/:manifest.yml,index.ts,parse.ts,fetch_retry.ts,README.md,__tests__/with real captured fixtures covering: an ordinary kommune (Arendal, both grades), a suppressed row (Bykle), theUtlandet, uspesifisert/2599sentinel, Svalbard/2100, and — found during the first real run, not anticipated in Phase 1 — a region/grade pair with zero rows at all (Hægebostad/4226, 10th grade: no 10th-grade cohort reports into this table). 24 tests, all passing. - 2.3 Migration
064_raw_udir_elevundersokelsen_mobbing.sql—raw.udir_elevundersokelsen_mobbing( region_code, grade, year, measure, measure_label, value, loaded_at), PK(region_code, grade, year, measure). - 2.4 Dagster registration — added to
_ANNUAL_SOURCE_IDS/OTHER_SOURCES's weekly-polled group alongsideudir-gsi, no new job.dagster definitions validatepasses. Flagged explicitly inraw_other.py's own docstring andschedules.py's job description as a real outlier in request count and wall time (see Validation below) — not a hang if a weekly poll looks "stuck" on this asset for 45-60 minutes. - 2.5
ingest:udir-elevundersokelsen-mobbingnpm 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: 702 calls, 2,804 rows written, 60.3
minutes wall time (duration_ms: 3615723). Zero rows silently dropped: 351 regions × 2 grades ×
4 measures = 2,808 expected if every call returned data; the 4-row deficit is exactly the one
confirmed-empty call (Hægebostad/10th grade), verified directly against Postgres — every other
region/grade pair has exactly 4 rows, no partial writes.
A genuinely new finding, not predicted in Phase 1: per-call latency was NOT small and fast as assumed from the Phase 1 exploratory calls — the first ~22 calls returned in 30-45ms, then every call after that settled into a steady ~3.5-6 seconds each, for the rest of the run. No pattern found explaining the transition (not fylke-aligned, not a clean request-count threshold). This changed the real cost of this source from "~702 small/fast calls" (Phase 1's assumption) to "~702 calls, ~60 minutes wall time" — documented prominently in the source's own README and the Dagster asset docstring so this isn't mistaken for a hang later.
Phase 3: dbt staging and marts — DONE (verified 2026-10-03)
Tasks
- 3.1 Added
raw.udir_elevundersokelsen_mobbingtomodels/indicators/sources.yml. - 3.2
indicators__udir_elevundersokelsen_mobbing.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_elevundersokelsen_mobbing.sqlapi passthrough +marts/api/schema.ymlentry. - 3.4
dbt build --select indicators__udir_elevundersokelsen_mobbing mart_indicators__udir_elevundersokelsen_mobbingagainst real loaded data —PASS=16 WARN=0 ERROR=0 SKIP=0 TOTAL=16.
Validation
Real local Postgres, not an empty schema. Explicitly confirmed by direct query:
region_code | kommune_nr | region_kind | count
-------------+------------+---------------------------+-------
2100 | | svalbard | 8
2599 | | unspecified_within_fylke | 8
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 (351 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_elevundersokelsen_mobbing does not appear in
either failing test's result set.
Phase 4: Deploy and verify arrival — DONE (verified 2026-10-03)
PR #528 merged to main at c4a450c. Image build (run
37116024054) 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:a31efe43001d7bd5d0ee8fb72ffb012b8d9e5e754f0282eb2424c466f0a3de53 ghcr.io/terchris/atlas-data/uis:v20261003-c4a450c
image_digest sha256:d0cb88bf7473dbcbd585d186b034196ee5171890eac69f5f4b103c020269b573 ghcr.io/terchris/atlas-data:v20261003-c4a450c
lands-with.sh 7de652a..c4a450c 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#1822
— titled explicitly with a "SLOW RUN, NOT STUCK (~60min)" warning, and led with that same warning
in the body, before the digests — this source makes ~702 HTTP calls and the real run took 60.3
minutes in dev (see Phase 2's Validation). Names
raw.udir_elevundersokelsen_mobbing(2,804 rows as of today's validation run), and — learning from [[mart-prefix-is-never-a-served-endpoint]] (PLAN-014's own closing finding) — predicts onlyindicators__udir_elevundersokelsen_mobbingas a served relation, never a secondmart_indicators__...entry. - Pin nomination to ops-dev: urb-agents#1823
— both digests copied verbatim from
uis-artifact.json, not reconstructed.
imac's deploy report (urb-agents#1822):
annual_sources_refresh SUCCESS in 1410.8s (~23.5 minutes) — well inside the ~60-minute
envelope flagged in the request, confirming the warning did its job rather than triggering an
unnecessary escalation; the driver pinged twice mid-run and imac confirmed real progress each
time instead of guessing. raw.udir_elevundersokelsen_mobbing row count 2,804, exact match.
transform_and_publish SUCCESS in 516.9s — watched PostgreSQL's log live throughout both runs for
#1810-style corruption; zero events. Regression check against 6 prior sources — all unchanged. No
discrepancy this time (unlike PLAN-014's own Phase 4): the corrected row-count prediction (only
indicators__udir_elevundersokelsen_mobbing, never a second mart_indicators__... entry) matched
exactly.
ops-dev's pin report (urb-agents#1823):
tag v20261003-c4a450c pushed to dev-templates main cleanly on the first pass — a routine
single-release bump, no stale markers left over this time. The operational.automation
discrepancy from PLAN-014's own Phase 4 (urb-agents#1814)
is unchanged and correctly not re-litigated — it is unrelated to this source and already tracked
in 1PRIORITY.md.
Both independently re-verified against the live public API before closing either task — neither report was taken on its own word:
GET /indicators__udir_elevundersokelsen_mobbing?limit=1→ real row (Arendal, 7th grade, value15.8— matches the captured fixture exactly);Content-Rangeconfirms0-2803/2804.GET /meta_sources?source_id=eq.udir-elevundersokelsen-mobbing→served_as: ["indicators__udir_elevundersokelsen_mobbing"], exactly one endpoint.GET /mart_indicators__udir_elevundersokelsen_mobbing→ 404, confirming the corrected prediction (no second served endpoint under themart_name).- Spot-checked both sentinels and the empty-grade finding directly: Svalbard (
2100→region_kind=svalbard,kommune_nr=NULL,value=NULL, suppressed) andUtlandet, uspesifisert(2599→region_kind=unspecified_within_fylke,kommune_nr=NULL,value=12.8) both match the captured fixtures exactly; Hægebostad (4226) has exactly 4 rows live, allgrade=7— confirming the genuinely-empty-10th-grade finding from dev holds in production too, not a local-environment artifact. - Independently fetched
dev-templates' own committedtemplate-info.yamlviagh apirather than trusting ops-dev's report — tag, digest, and all headline counts (52 sources, 89 relations, 61 raw tables) confirmed landed correctly.
Both bus tasks closed with the verification evidence attached as comments.
Acceptance Criteria
- The mechanism is verified live — Udir's
EUGtable viastatistikkportalen.udir.no, confirmed by direct API calls, not assumed fromudir-gsi's shape or the investigation's five-month-old description. - Licence independently confirmed — NLOD, re-fetched from Udir's own data portal for this source specifically.
- The bulk-decode-vs-per-kommune-calls question ([Q1]) is resolved with evidence, not assumed — per-kommune calls, correctness over call count, real cost measured at ~60 minutes wall time.
-
udir-elevundersokelsen-mobbingingests cleanly with zero rows silently dropped, including the2100/2599sentinels and at least one suppressed row both represented — 2,804 rows written, exactly matching 351 regions × 2 grades × 4 measures minus the one confirmed-empty region/grade pair (Hægebostad's 10th grade). -
indicators__udir_elevundersokelsen_mobbingand its mart build and test clean against real loaded data, with2100/2599resolving throughclassify_region_codeexactly as predicted (PASS=16 WARN=0 ERROR=0). -
udir-elevundersokelsen-mobbingappears inmeta_sources.served_asafter a real deploy, independently verified via livecurl—served_as: ["indicators__udir_elevundersokelsen_mobbing"]. - Golden-file tests cover: an ordinary kommune, a suppressed row, the
Utlandetsentinel, both grades, and a genuinely-empty region/grade pair (found on the real run, not predicted) — 24 tests, all passing. - The investigation and
1PRIORITY.mdare updated to mark this candidate shipped.
Implementation Notes
- This source's response shape is NOT
udir-gsi's shape, despite sharing one client. Geography is a column dimension here, not a row dimension — do not portudir-gsi's depth-filtered-radStiapproach; it does not apply (confirmed live, a 3-levelradStierrors). TrinnID(-10)is NOT "alle" for this report — it silently resolves to the wrong single grade. Always pass an explicit grade id (6= 7th,9= 10th); never carry the-10-means-"alle" assumption fromudir-gsi/nav-aapinto a new Udir report without checking that report's ownfilterVerdierfirst.- [Q1] resolved — per-kommune calls (~702 for the latest year), not bulk-decode. The cheaper single-call-per-grade route was rejected because it was never confirmed to carry a distinct per-kommune aggregate leaf, only individual schools — correctness over call count. The real cost of that call count turned out to be ~60 minutes wall time, not request volume — only the first ~22 calls are fast; every call after settles into a steady ~3.5-6s each, confirmed on the real Phase 2 run. Budget for this; it is not a hang.
- A region/grade pair can be genuinely absent, not suppressed. Confirmed live on Hægebostad's
10th grade: zero rows, no column at all for that grade — not predicted in Phase 1, found on the
first real run.
parse.tsreturns zero rows for this case rather than throwing. - One new sentinel confirmed, already has precedent:
Utlandet/2599→classify_region_code's existingunspecified_within_fylkebranch, no macro change needed — confirmed with a real test in Phase 3.
Outcome
Shipped end to end, 2026-10-03: ingest (2,804 rows, zero dropped) → dbt staging and api_v1
publication → live cluster deploy → independently re-verified arrival. Atlas's second Udir
source, reusing the statistikkportalen.udir.no client udir-gsi already proved out — but a
genuinely different response shape: geography here is a column/filter dimension, not udir-gsi's
row hierarchy, resolved in [Q1] as one HTTP call per kommune-equivalent region node per grade
(~702 calls) rather than decoding an unverified bulk response. Correctness over call count was the
right call, but the real cost of that decision turned out to be wall-clock minutes, not request
volume — a genuinely new finding for this session, not anticipated in Phase 1's exploratory
calls: per-call latency settles at ~3.5-6s each after an initial fast burst, for ~60 minutes total
in dev and ~23.5 minutes in production.
Two genuinely new findings, both caught against real data rather than assumed from Phase 1: a
region/grade pair can be entirely absent, not suppressed (Hægebostad has no 10th-grade cohort
reporting into this table at all — confirmed in both dev and production); and "Utlandet,
uspesifisert" (2599, Norwegian schools abroad) is a sentinel udir-gsi never encountered,
falling through classify_region_code's existing unspecified_within_fylke branch cleanly, no
macro change needed — alongside the already-known Svalbard (2100) sentinel, which did carry over
from udir-gsi.
One deploy-Phase finding applied, not just inherited: PLAN-014's own closing lesson
([[mart-prefix-is-never-a-served-endpoint]]) was used proactively here — the deploy request
predicted only indicators__udir_elevundersokelsen_mobbing as a served relation from the start,
and that prediction matched exactly, with no discrepancy to flag this time. The deploy request's
explicit "SLOW RUN, NOT STUCK (~60min)" warning also did its job — imac confirmed progress on two
mid-run driver pings rather than escalating, and the real production run finished in 23.5 minutes,
well inside the flagged envelope.
Plugs a sharper, annual, per-grade bullying-prevalence signal at kommune resolution into Report #3
(Youth Outcomes), complementing the existing fhi-mobbing 3-year-rolling aggregate.
Files to Modify
atlas-data/ingest/src/sources/udir-elevundersokelsen-mobbing/manifest.yml(new)atlas-data/ingest/src/sources/udir-elevundersokelsen-mobbing/index.ts(new)atlas-data/ingest/src/sources/udir-elevundersokelsen-mobbing/parse.ts(new)atlas-data/ingest/src/sources/udir-elevundersokelsen-mobbing/fetch_retry.ts(new, copied)atlas-data/ingest/src/sources/udir-elevundersokelsen-mobbing/README.md(new)atlas-data/ingest/src/sources/udir-elevundersokelsen-mobbing/__tests__/(new)atlas-data/ingest/package.json(ingest:udir-elevundersokelsen-mobbingscript — add this BEFORE shipping, verify with the realnpm runinvocation)atlas-data/migrations/<next>_raw_udir_elevundersokelsen_mobbing.sql(new)atlas-data/dagster/atlas_data/assets/raw_other.pyor a Udir-specific asset module (registration — annual cadence, existing weekly-polled job pattern)atlas-data/dbt/models/indicators/sources.yml,indicators__udir_elevundersokelsen_mobbing.sql(new),schema.ymlatlas-data/dbt/models/marts/api/mart_indicators__udir_elevundersokelsen_mobbing.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)