feat(diagnose): first bottleneck diagnosis on public Norwegian figures

paper/norge_2025.json holds 2025 and October 2026 figures for police and
prosecution, district courts, appeal courts and prison, each with its
source (POD report 1/2026, Domstoladministrasjonen annual report 2025,
Kriminalomsorgen key figures October 2026 and annual report 2025, SSB).
Derived values are marked as estimates.

Result: police and prosecution bind (utilization 1.09 once dismissals
for lack of capacity are counted as untreated), with prison (1.06) and
district courts (1.04) also above full utilization; appeal courts are
below (0.88). README shows the table and its caveats.

diagnose.py accepts an acceptable backlog instead of a target time, and
cases closed without treatment, and lists other links above capacity.
The stylized example still reproduces section 9.5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-11-10 13:37:12 +01:00
commit 1723d593f8
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
5 changed files with 68 additions and 15 deletions

View file

@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file.
## 2026-11-10 — flaskehalsdiagnose med egne tall
- Nytt skript `paper/diagnose.py` (første versjon): fra innstrømning, kapasitet, restanse og mål per ledd til leddet som binder, tiltakstypen og kapasitetsbehovet, etter notatets metode. Eksempelfila gjenskaper avsnitt 9.5. Forløpet over tid er ikke med ennå.
- Første diagnose på offentlige norske tall (`paper/norge_2025.json`, med kilde per tall): politi og påtale binder (utnyttelse 1,09 når kapasitetshenleggelsene regnes med), fengsel (1,06) og tingrett (1,04) ligger også over full utnyttelse. `diagnose.py` godtar nå akseptabel restanse og saker som avsluttes uten behandling.
## 2026-11-10 — PDF-ene bygd på nytt fra kilden

View file

@ -11,7 +11,7 @@ Concept note (Norwegian, LaTeX) and its simulation code. The principle: enforcem
## Conventions
- The core sentence must be quoted verbatim everywhere: «Innsatsen for å håndheve lover og regler bør løpende stå i forhold til hvor mye, og hvor alvorlig, de faktisk brytes.» Short form of the clarification: «Straffebud og strafferammer ligger fast.»
- Any change to `sim.py` must keep `paper/data/` byte-identical unless the change is meant to alter results; then text, figures and PDFs change in the same commit.
- Licensing is split: text (`pdf/`, `paper/*.tex`) is CC BY 4.0 (`LICENSE-CC-BY-4.0.txt`); code and data (`paper/sim.py`, `paper/diagnose.py`, `paper/eksempel_domstol.json`, `requirements.txt`, `paper/data/`) are MIT (`LICENSE`). New files follow the same split.
- Licensing is split: text (`pdf/`, `paper/*.tex`) is CC BY 4.0 (`LICENSE-CC-BY-4.0.txt`); code and data (`paper/sim.py`, `paper/diagnose.py`, `paper/eksempel_domstol.json`, `paper/norge_2025.json`, `requirements.txt`, `paper/data/`) are MIT (`LICENSE`). New files follow the same split.
- Never commit anything under `private/` (working documents: peer review, change list, social-media drafts, original source zip).
## Commands

View file

@ -44,6 +44,28 @@ Svaret viser utnyttelse og restanse i hvert ledd, leddet som binder og tiltakene
Denne første versjonen viser ikke forløpet over tid, og den sjekker ikke stabilitetsgrensen for politiets reaksjon.
### Første forsøk med norske tall
`paper/norge_2025.json` har offentlige tall for 2025 og oktober 2026 fra Politidirektoratet og Riksadvokaten, Domstoladministrasjonen, Kriminalomsorgen og SSB. Kilden til hvert tall står i fila.
```bash
python3 diagnose.py norge_2025.json
```
| Ledd | Utnyttelse | Restanse |
|---|---|---|
| Politi og påtale | 1,09 | 31 900 saker eldre enn tre måneder, opp vel 5 % på ett år |
| Tingrett, meddomsrettssaker | 1,04 | 3 777, opp 15 % |
| Lagmannsrett, bevisanker | 0,88 | 290, ned |
| Fengsel | 1,06 | soningskø 657, mot 284 ett år før |
Politi og påtale avgjorde litt flere saker enn de fikk inn i 2025. Det ser ut som balanse, men om lag 30 300 av sakene ble henlagt fordi kapasiteten manglet. Regnes de som saker som ikke ble behandlet, er politi og påtale det leddet som binder. Fengsel og tingrett ligger også over full utnyttelse, og der flytter skranken seg når den første er løst. Det stemmer med det notatet sier: presset ligger i politi og påtale og i straffegjennomføringen, mer enn i tingrettene.
Forbehold:
- Tallene for fengsel er anslag. Avgangen er regnet ut fra andelen av saker i fengsel, og innstrømningen er avgangen pluss køveksten.
- Hvert ledd har sine egne saksenheter, så bare utnyttelsen kan sammenlignes på tvers av ledd.
- Skriptet anslår at politi og påtale trenger 15 % mer kapasitet for å behandle alle anmeldte saker uten kapasitetshenleggelser. Det er et grovt anslag. Henlagte saker krever ikke nødvendigvis like mye arbeid som de andre, og andelen som kan gå til forenklede reaksjoner, er ikke kjent.
## Hva notatet gjør
- Formulerer prinsippet som én beslutningsregel fra det observerte kriminalitetsbildet til en samlet tiltaksvektor: innsats, reaksjonsformer, kapasitet, prosessuell kvalitet og lovbestemt handlingsrom.
@ -72,7 +94,7 @@ Dette er et konseptnotat og en invitasjon til videre forskning, ikke et ferdig v
| Mappe | Innhold |
|---|---|
| `pdf/` | Ferdige PDF-er av notatet, sammendraget og lysarkene |
| `paper/` | LaTeX-kilden (`main.tex` er notatet, `notat.tex` sammendraget, `karusell.tex` lysarkene), simuleringen `sim.py` og flaskehalsdiagnosen `diagnose.py` med eksempelfil |
| `paper/` | LaTeX-kilden (`main.tex` er notatet, `notat.tex` sammendraget, `karusell.tex` lysarkene), simuleringen `sim.py` og flaskehalsdiagnosen `diagnose.py` med eksempelfil og norske tall |
| `paper/data/` | Figurdataene som `sim.py` skriver og LaTeX-kilden leser |
PDF-ene er bygd fra LaTeX-kilden i dette repoet med TeX Live 2026: notatet og sammendraget med pdflatex (tre ganger, fra `paper/`), lysarkene med xelatex (to ganger), som trenger fonten Inter fra TeX Live.
@ -86,7 +108,7 @@ Innspill og kritikk er velkomne, særlig fra dem som arbeider i straffesakskjede
## Lisens
- Teksten, det vil si PDF-ene i `pdf/` og LaTeX-kilden (`.tex`-filene i `paper/`), er lisensiert under [CC BY 4.0](LICENSE-CC-BY-4.0.txt). Du kan dele og bearbeide den fritt, også i andres arbeid, så lenge du oppgir kilden: Kjell Tore Guttormsen (2026), *Den dynamiske rettsstaten*, versjon 2.
- Koden, det vil si `paper/sim.py`, `paper/diagnose.py`, `requirements.txt`, figurdataene i `paper/data/` og `paper/eksempel_domstol.json`, er lisensiert under [MIT](LICENSE).
- Koden, det vil si `paper/sim.py`, `paper/diagnose.py`, `requirements.txt`, figurdataene i `paper/data/`, `paper/eksempel_domstol.json` og `paper/norge_2025.json`, er lisensiert under [MIT](LICENSE).
## Changelog
See [CHANGELOG.md](CHANGELOG.md).

View file

@ -14,7 +14,10 @@ strømmer i saker per måned, beholdninger i saker, tider i måneder:
"maal_tid": 3, # mål for saksbehandlingstid (mnd)
"prognose_innstromning": 134, # valgfritt: strømmen når balansen er gjenopprettet
"maks_andel_utenom": 0.2, # valgfritt: andel som kan ledes til forenklede reaksjoner
"restanse_for_12_mnd": 400} # valgfritt: gir observert vekst i restansen
"restanse_for_12_mnd": 400, # valgfritt: gir observert vekst i restansen
"restanse_maal": 300, # valgfritt: akseptabel restanse (saker) i stedet for maal_tid
"skjult_avgang": 0} # valgfritt: saker per måned som avsluttes uten behandling
# (f.eks. henlagt pga. kapasitet); trekkes fra kapasiteten
]}
Verktøyet følger trinn (c)-(e) i metoden (artikkelen, avsnitt 9.4-9.5):
@ -44,18 +47,20 @@ def tall(x, d=2):
def analyser(ledd):
F = float(ledd["innstromning"])
Z = float(ledd["kapasitet"])
Z = float(ledd["kapasitet"]) - float(ledd.get("skjult_avgang", 0.0))
Q = float(ledd["restanse"])
tid_maal = float(ledd["maal_tid"])
tid_maal = float(ledd["maal_tid"]) if "maal_tid" in ledd else None
Q_ref = float(ledd["restanse_maal"]) if "restanse_maal" in ledd else tid_maal * Z
ups = F / Z
tid = Q / Z # Littles lov: beholdning / avgangsrate
# Littles lov: beholdning / avgangsrate; bare meningsfull når restansen er hele køen
tid = Q / Z if "restanse_maal" not in ledd else None
if "restanse_for_12_mnd" in ledd:
vekst = (Q - float(ledd["restanse_for_12_mnd"])) / 12.0
vekst_kilde = "observert siste 12 mnd"
else:
vekst = F - min(F, Z) # det som ikke avgjøres, legger seg i kø
vekst_kilde = "innstrømning minus kapasitet"
return dict(navn=ledd["navn"], F=F, Z=Z, Q=Q, tid_maal=tid_maal, ups=ups, tid=tid,
return dict(navn=ledd["navn"], F=F, Z=Z, Q=Q, Q_ref=Q_ref, tid_maal=tid_maal, ups=ups, tid=tid,
vekst=vekst, vekst_kilde=vekst_kilde,
Fp=float(ledd.get("prognose_innstromning", F)),
andel=float(ledd.get("maks_andel_utenom", 0.0)))
@ -65,8 +70,7 @@ def tiltak(a):
"""Trinn (d) og (e) for det bindende leddet."""
ut = []
xi_min = 1.0 - a["andel"]
Q_ref = a["tid_maal"] * a["Z"] # kø som svarer til målet for saksbehandlingstid
Q_exc = max(a["Q"] - Q_ref, 0.0)
Q_exc = max(a["Q"] - a["Q_ref"], 0.0) # restanse over målet
ut.append("1. Utnytt leddet: fjern tapt tid i skranken (ikke regnet her).")
if a["andel"] > 0:
ups_u = xi_min * a["Fp"] / a["Z"]
@ -84,8 +88,9 @@ def tiltak(a):
f"Restanse over målet: {tall(Q_exc, 0)} saker, avviklet over {T_CLEAR:.0f} mnd.")
ut.append(f" Kapasitet: ({tall(xi_min)} x {tall(a['Fp'], 1)} + {tall(Q_exc, 0)}/{T_CLEAR:.0f}) / "
f"{tall(UPS_STAR)} = {tall(behov, 1)} saker/mnd, det vil si {tall(okning * 100, 0)} % mer.")
ut.append(f" Bestilt kapasitet virker gradvis fra {L1:.0f} til {L2:.0f} mnd etter vedtak. "
"Når restansen er avviklet, går andelen utenom tilbake, og behovet vurderes på nytt.")
ut.append(f" Bestilt kapasitet virker gradvis fra {L1:.0f} til {L2:.0f} mnd etter vedtak."
+ (" Når restansen er avviklet, går andelen utenom tilbake, og behovet vurderes på nytt."
if a["andel"] > 0 else ""))
else:
ut.append("3. Utvid: ikke nødvendig med disse tallene.")
return ut
@ -98,7 +103,9 @@ def main(fil):
print("Trinn (c): utnyttelse og restanse i hvert ledd\n")
print(f"{'Ledd':<22}{'Utnyttelse':>11}{'Saksbeh.tid':>13}{'Mål':>6}{'Restansevekst/mnd':>19}")
for a in rader:
print(f"{a['navn']:<22}{tall(a['ups']):>11}{tall(a['tid'], 1):>13}{tall(a['tid_maal'], 1):>6}"
maal = tall(a['tid_maal'], 1) if a['tid_maal'] is not None else "-"
tid = tall(a['tid'], 1) if a['tid'] is not None else "-"
print(f"{a['navn']:<22}{tall(a['ups']):>11}{tid:>13}{maal:>6}"
f"{tall(a['vekst'], 0):>19}")
# skranken: høyest utnyttelse, deretter raskest voksende restanse relativt til kapasiteten
b = max(rader, key=lambda a: (round(a["ups"], 2), a["vekst"] / a["Z"]))
@ -106,8 +113,12 @@ def main(fil):
if b["ups"] <= UPS_HI and b["vekst"] <= 0:
print(f"Ingen ledd binder: høyeste utnyttelse er {tall(b['ups'])} ({b['navn']}), og ingen restanse vokser.")
return
print(f"Skranken som binder: {b['navn']} (utnyttelse {tall(b['ups'])}, saksbehandlingstid "
f"{tall(b['tid'], 1)} mnd mot mål {tall(b['tid_maal'], 1)}; vekst: {b['vekst_kilde']}).")
print(f"Skranken som binder: {b['navn']} (utnyttelse {tall(b['ups'])}, restanse {tall(b['Q'], 0)} "
f"mot mål {tall(b['Q_ref'], 0)}; vekst: {b['vekst_kilde']}).")
andre = [a["navn"] for a in rader if a is not b and (a["ups"] > UPS_HI or a["vekst"] > 0)]
if andre:
print(f"Også over full utnyttelse eller med voksende restanse: {', '.join(andre)}. "
"Skranken flytter seg dit når den første er løst.")
nedstroms = [a["navn"] for a in rader[rader.index(b) + 1:]]
if nedstroms:
print(f"Merk: tallene for {', '.join(nedstroms)} viser bare det som slipper gjennom skranken. "

19
paper/norge_2025.json Normal file
View file

@ -0,0 +1,19 @@
{
"merknad": "Offentlige norske tall, per måned (årstall delt på 12). Hvert ledd har sine egne saksenheter; verktøyet sammenligner bare utnyttelse og restanse innen hvert ledd. Tall merket anslag er utledet, ikke oppgitt i kilden.",
"kilder": {
"Politi og påtale": "POD-rapport 1/2026, vedlegg 1 til politiets årsrapport 2025 (Politiet og Riksadvokaten), avsnitt 3.1-3.2: anmeldte lovbrudd 2025 348 534, påtaleavgjorte saker 349 450, ikke påtaleavgjorte saker eldre enn tre måneder (IPA 3) om lag 31 900 ved utgangen av 2025, «vel 5 prosent» flere enn året før (restanse_for_12_mnd = 31 900 / 1,05, anslag), målkrav: restanse høyst 10 prosent av innkomne saker. Henlagt på grunn av manglende saksbehandlingskapasitet: om lag 30 300 i 2025 (SSB, 2. november 2026, som i notatet).",
"Tingrett, meddomsrettssaker": "Domstoladministrasjonen, Årsrapport 2025, s. 16: innkomne 13 885, avgjorte 13 403, restanse 3 777 (+15 prosent, restanse_for_12_mnd = 3 777 / 1,15, anslag), saksbehandlingstid 3,1 mnd, mål 3 mnd.",
"Lagmannsrett, bevisanker": "Domstoladministrasjonen, Årsrapport 2025, s. 19: bevisanker med strafferamme 6 år og lavere og over 6 år lagt sammen: innkomne 378 + 220, avgjorte 427 + 253, restanse 160 + 130, saksbehandlingstid 6,2 mnd, mål 3 mnd i 2025 (5 mnd fra 2026).",
"Fengsel": "Kriminalomsorgen, Nøkkeltall oktober 2026 (pressemelding 6.11.2026): soningskø 657 ubetingede dommer, mot 284 i oktober 2025; belegg 91 prosent. Årsrapport 2025 (KDI): om lag 16 000 saker i fengsel og samfunn, 58 prosent i samfunn, snittkø 2025 364 dommer («lavt nivå»). Anslag: avgang til fengsel = 16 000 x 0,42 / 12 = 560 per mnd; innstrømning = avgang + køvekst (657 - 284) / 12 = 591 per mnd; restanse_maal = snittkøen i 2025."
},
"ledd": [
{"navn": "Politi og påtale", "innstromning": 29044.5, "kapasitet": 29120.8, "skjult_avgang": 2525.0,
"restanse": 31900, "restanse_for_12_mnd": 30381, "restanse_maal": 34853},
{"navn": "Tingrett, meddomsrett", "innstromning": 1157.1, "kapasitet": 1116.9,
"restanse": 3777, "restanse_for_12_mnd": 3284, "maal_tid": 3},
{"navn": "Lagmannsrett, bevisanke", "innstromning": 49.8, "kapasitet": 56.7,
"restanse": 290, "maal_tid": 3},
{"navn": "Fengsel", "innstromning": 591, "kapasitet": 560,
"restanse": 657, "restanse_for_12_mnd": 284, "restanse_maal": 364}
]
}