feat(forlop): time course and stability check from own numbers
New script paper/forlop.py reads the same input file as diagnose.py. It checks whether the steering tolerates its own delay, places the binding link in the model as the link with a queue, calibrates the size and start of the increase so that the coordinated rule's first budget decision sees the numbers in the file, and runs simulate_endo month by month with and without measures. It also reports what the measure does to the rest of the chain: police effort, the share diverted, and whether the bottleneck moves to the links after it. The example file reproduces section 9.5 and table 10: +19 % now, +12 % after 12 months, capacity 1,31; 8,3 / 0,74 / 1,10 / 3,5 with measures and 68,7 / 0,65 / 1,26 without. The stability block reproduces section 9.3 with delay 11 and reactivity 3. The script declines to print a time course when the steering is unstable, when the forecast has not settled within the horizon, or when the model cannot build the backlog in the file before the first decision. sim.py is imported unchanged; paper/data is untouched. diagnose.py gives identical output for both input files; the bottleneck selection is now a function so forlop.py can reuse it, and true/false are rejected where a number is expected. Reviewed by an independent agent; its 14 findings are addressed. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
parent
4a9170c3b7
commit
01d335e941
6 changed files with 468 additions and 15 deletions
|
|
@ -2,6 +2,12 @@
|
|||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## 2026-11-10 — forløp over tid med egne tall
|
||||
|
||||
- Nytt skript `paper/forlop.py`: leser samme fil som `diagnose.py`, sjekker om styringen tåler sin egen forsinkelse, og viser måned for måned hva som skjer når anbefalingen følges, sammenlignet med å ikke gjøre noe i kjeden. Det viser også hva tiltaket gjør med resten av kjeden: politiets innsats, andelen ledet utenom, og om skranken flytter seg til leddene etter. Eksempelfila gjenskaper avsnitt 9.5 (19 % nå, 12 % året etter, kapasitet 1,31 til slutt). Bare leddet som binder, har kø i modellen. Skriptet regner ikke forløpet når styringen er ustabil eller for nær grensen, eller når restansen i fila er større enn modellen rekker å bygge opp før første vedtak.
|
||||
- `paper/eksempel_domstol.json` har fått feltet `"styring"` (forsinkelse og reaktivitet). `diagnose.py` gir samme svar som før, og avviser nå `true` og `false` der det skal stå tall.
|
||||
- `paper/sim.py`: fire henvisninger i kommentarer og én overskrift i utskriften viste til gamle lignings- og tabellnumre. De er rettet til (12), (28) og tabell 3. Datafilene er uendret (22 av 22 byte for byte like).
|
||||
|
||||
## 2026-11-10 — kvalitetssjekk før publisering
|
||||
|
||||
- Rettet fengselsanslaget i `paper/norge_2025.json`: avgangen var regnet fra «saker håndtert» og er nå regnet fra iverksatte saker (Kriminalomsorgen, Årsrapport 2025, s. 70). Køen til fengsel er skilt fra køen til fotlenke. Restansen i politiet er hentet direkte fra POD-rapporten (31 908 mot 30 295), og sidetallene til Domstoladministrasjonens årsrapport er rettet (s. 15 og 18).
|
||||
|
|
|
|||
|
|
@ -11,13 +11,14 @@ 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`, `README.md`, `CHANGELOG.md`, `CLAUDE.md`) 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).
|
||||
- Licensing is split: text (`pdf/`, `paper/*.tex`, `README.md`, `CHANGELOG.md`, `CLAUDE.md`) is CC BY 4.0 (`LICENSE-CC-BY-4.0.txt`); code and data (`paper/sim.py`, `paper/diagnose.py`, `paper/forlop.py`, `paper/eksempel_domstol.json`, `paper/norge_2025.json`, `requirements.txt`, `paper/data/`) are MIT (`LICENSE`). New files follow the same split.
|
||||
- Never commit working documents; `private/` is ignored for that purpose.
|
||||
|
||||
## Commands
|
||||
- Setup: `python3 -m venv .venv && .venv/bin/pip install -r requirements.txt`
|
||||
- Run simulation (about 2 min, from `paper/`): `../.venv/bin/python sim.py`
|
||||
- Bottleneck diagnosis from own numbers (stdlib only, from `paper/`): `python3 diagnose.py eksempel_domstol.json`; must reproduce section 9.5 (utilization 1,28, +19 % capacity). `python3 diagnose.py norge_2025.json` must match the table in README (1,09 / 1,04 / 0,88 / 1,09).
|
||||
- Time course and stability check from own numbers (needs the venv, from `paper/`): `../.venv/bin/python forlop.py eksempel_domstol.json`; must reproduce section 9.5 and table 10 (critical delay 7,2; +19 % now, +12 % after 12 months, capacity 137,9 = 1,31; with measures 8,3 / 0,74 / 1,10 / +31 % / 3,5; without 68,7 / 0,65 / 1,26). `forlop.py` imports `sim.py` and must never require a change there.
|
||||
- Reproduction check after a run: `git status paper/data` shows no changes (verified 2026-11-10: 22/22 files identical with Python 3.14.0, numpy 2.5.4, scipy 1.18.1)
|
||||
- LaTeX build (from `paper/`, TeX Live 2026 in `/Library/TeX/texbin`): `pdflatex main.tex` and `pdflatex notat.tex` three times each, `xelatex karusell.tex` twice; copy to `pdf/` (verified 2026-11-10: main 58 pages, notat 2, karusell 16, no undefined references). Build in a scratch copy of `paper/` to keep aux files out of the repo, and give each run a time limit: one pdflatex run has been seen to hang.
|
||||
|
||||
|
|
|
|||
32
README.md
32
README.md
|
|
@ -44,7 +44,27 @@ python3 diagnose.py eksempel_domstol.json
|
|||
|
||||
Svaret viser utnyttelse og restanse i hvert ledd, leddet som binder og tiltakene i rekkefølgen utnytt, led saker utenom, utvid. For leddet som binder, regner skriptet også ut hvor mye kapasitet som trengs, etter prognosen for strømmen og ikke etter køen. Formatet på inndata står øverst i skriptet. Eksempelfila har stiliserte tall, ikke norske tall, og gjenskaper tallene i avsnitt 9.5: utnyttelse 1,28 og 19 prosent mer kapasitet.
|
||||
|
||||
Denne første versjonen viser ikke forløpet over tid, og den sjekker ikke stabilitetsgrensen for politiets reaksjon. Den regner heller ikke med kapasitet som er bevilget, men ikke virker ennå: oppgi kapasiteten som virker nå, og trekk det som allerede er bevilget, fra behovet. Kolonnen for saksbehandlingstid er restanse delt på kapasitet, ikke målt saksbehandlingstid.
|
||||
Diagnosen regner ikke med kapasitet som er bevilget, men ikke virker ennå: oppgi kapasiteten som virker nå, og trekk det som allerede er bevilget, fra behovet. Kolonnen for saksbehandlingstid er restanse delt på kapasitet, ikke målt saksbehandlingstid.
|
||||
|
||||
### Følg tiltaket over tid
|
||||
|
||||
`paper/forlop.py` leser samme fil og viser hva som skjer måned for måned når anbefalingen følges, og om styringen tåler sin egen forsinkelse. Skriptet bruker simuleringen og trenger derfor numpy og scipy, som i installasjonen over.
|
||||
|
||||
```bash
|
||||
../.venv/bin/python forlop.py eksempel_domstol.json
|
||||
```
|
||||
|
||||
Svaret har tre deler:
|
||||
|
||||
- **Stabilitetssjekk.** I fila oppgir du forsinkelsen fra kriminalitetsbildet endrer seg til innsatsen er endret, og hvor kraftig innsatsen reagerer (`"styring"`). Skriptet sier om forsinkelsen er kortere enn den kritiske, og hvor kraftig reaksjonen kan være. Med eksempelfila er den kritiske forsinkelsen 7,2 måneder mot 3, altså stabilt. Er styringen ustabil eller for nær grensen, regner skriptet ikke forløpet, og sier hvorfor.
|
||||
- **Forløp.** Leddet som binder, legges inn i modellen med dine tall for kapasitet, prognose, restanse og mål. Du ser budsjettvedtakene, kapasiteten, andelen ledet utenom, restansen og saksbehandlingstiden, og en sammenligning med å ikke gjøre noe i kjeden. Eksempelfila gjenskaper avsnitt 9.5: 19 prosent mer kapasitet nå, 12 prosent til året etter, og en kapasitet på 1,31 ganger utgangsnivået til slutt.
|
||||
- **Resten av kjeden.** Politiets innsats, andelen påtalemyndigheten leder utenom, og hva flere avgjorte saker betyr for leddene som står etter i fila. I eksempelet flytter skranken seg til kriminalomsorgen (utnyttelse 1,13 til slutt).
|
||||
|
||||
Forbehold:
|
||||
- Bare leddet som binder, har kø i modellen, og det regnes som leddet som tar imot sakene politiets innsats gir (domstolen i notatet). For leddene etter regner skriptet bare hva flere avgjorte saker betyr hvis de får sakene sine gjennom skranken. Hvor sakene som ledes utenom havner, er ikke regnet. Leddene foran vises som politiets innsats og andelen ledet utenom.
|
||||
- Kapasitet, restanse og saksbehandlingstid følger tallene dine. Kriminalitet, tillit og politiets innsats følger modellens stiliserte parametere, som ikke er kalibrert på norske data. Rekkefølgen og retningen er poenget, ikke tallene.
|
||||
- Modellen starter med et ledd i balanse, en varig økning og første budsjettvedtak høyst 15 måneder senere. Er restansen din så stor at modellen ikke rekker å bygge den opp på den tiden, regner skriptet ikke forløpet. Da gjelder diagnosen alene. Mindre avvik skriver skriptet ut.
|
||||
- Stabilitetssjekken gjelder likevekten før økningen og bruker modellens stiliserte tall for hvor fort innsatsen virker.
|
||||
|
||||
### Første forsøk med norske tall
|
||||
|
||||
|
|
@ -69,6 +89,7 @@ Forbehold:
|
|||
- Hvert ledd har sine egne saksenheter, så bare utnyttelsen kan sammenlignes på tvers av ledd. Politiets rapport og SSB teller heller ikke det samme. Politiets driftsstatistikk har 348 534 anmeldte lovbrudd og 349 450 påtaleavgjorte saker. SSBs offisielle statistikk har 308 438 etterforskede lovbrudd og holder utenfor om lag 35 300 anmeldelser som ble henlagt utenfor straffesak. Regnes kapasitetshenleggelsene som SSBs andel (9,8 % av de etterforskede lovbruddene) i stedet for som et antall, blir utnyttelsen for politi og påtale 1,11 i stedet for 1,09.
|
||||
- Kapasitetshenleggelsene er en registrert begrunnelse. Politidirektoratet og riksadvokaten melder om forholdsvis store forskjeller mellom politidistriktene i bruken av henleggelseskoder. Riksadvokatens nye rundskriv om henleggelser (nr. 2/2025) gjelder fra 1. januar 2026, så tall fra 2026 kan ikke uten videre sammenlignes med tidligere år.
|
||||
- Skriptet anslår at fengslene trenger om lag 15 % mer kapasitet (358 mot 313 iverksettelser i måneden). Om lag 9 % skal til for å holde følge med strømmen. Resten er margin, fordi verktøyet dimensjonerer til 95 % utnyttelse. Det er et grovt anslag.
|
||||
- Forløpet over tid (`../.venv/bin/python forlop.py norge_2025.json`) gir for fengsel 15 % mer kapasitet nå og 7 % til året etter, fordi køen vokser mens den første kapasiteten bygges opp. Det er modellens stiliserte dynamikk lagt på norske størrelser, ikke en prognose. Køen til fengsel er dessuten under målet på to måneder, mens modellen starter på målet.
|
||||
|
||||
## Hva notatet gjør
|
||||
|
||||
|
|
@ -81,10 +102,11 @@ Forbehold:
|
|||
|
||||
## Status og begrensninger
|
||||
|
||||
Dette er et konseptnotat og en invitasjon til videre forskning. Flaskehalsdiagnosen kan brukes på egne tall nå. Resten av modellen er en første versjon.
|
||||
Dette er et konseptnotat og en invitasjon til videre forskning. Flaskehalsdiagnosen og forløpet over tid kan brukes på egne tall nå. Resten av modellen er en første versjon.
|
||||
|
||||
- Selve modellen er ikke kalibrert på norske data. Parameterne er stiliserte, og simuleringene illustrerer mekanismer. De er ikke prognoser.
|
||||
- Hvilket ledd i kjeden som faktisk binder, er et empirisk spørsmål. Domstolen er et gjennomregnet eksempel. Dagens tall peker mot politi, påtale og straffegjennomføring, og det første, grove forsøket på norske tall peker samme vei, med forbeholdene over. Diagnosen viser ikke ennå forløpet over tid med egne tall.
|
||||
- Hvilket ledd i kjeden som faktisk binder, er et empirisk spørsmål. Domstolen er et gjennomregnet eksempel. Dagens tall peker mot politi, påtale og straffegjennomføring, og det første, grove forsøket på norske tall peker samme vei, med forbeholdene over.
|
||||
- «Resten av kjeden» betyr i denne versjonen politiets innsats, andelen påtalemyndigheten leder utenom, og strømmen videre til leddene etter. Bare ett ledd har kø i modellen. I notatets simuleringer er det domstolen; i `forlop.py` er det leddet som binder i dine tall.
|
||||
- Kostnader og gevinster er ikke regnet i kroner.
|
||||
- Avsnitt 10 i notatet beskriver begrensningene og har en veiplan i åtte punkter for det som gjenstår.
|
||||
|
||||
|
|
@ -99,7 +121,7 @@ Dette er et konseptnotat og en invitasjon til videre forskning. Flaskehalsdiagno
|
|||
| 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 og norske tall |
|
||||
| `paper/` | LaTeX-kilden (`main.tex` er notatet, `notat.tex` sammendraget, `karusell.tex` lysarkene), simuleringen `sim.py`, flaskehalsdiagnosen `diagnose.py` og forløpet `forlop.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.
|
||||
|
|
@ -113,7 +135,7 @@ Innspill og kritikk er velkomne, særlig fra dem som arbeider i straffesakskjede
|
|||
## Lisens
|
||||
|
||||
- Teksten, det vil si PDF-ene i `pdf/`, LaTeX-kilden (`.tex`-filene i `paper/`) og dokumentasjonen (`README.md`, `CHANGELOG.md`, `CLAUDE.md`), 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/`, `paper/eksempel_domstol.json` og `paper/norge_2025.json`, er lisensiert under [MIT](LICENSE).
|
||||
- Koden, det vil si `paper/sim.py`, `paper/diagnose.py`, `paper/forlop.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).
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Flaskehalsdiagnose for straffesakskjeden fra egne tall (første versjon).
|
||||
"""Flaskehalsdiagnose for straffesakskjeden fra egne tall.
|
||||
|
||||
Bruk (fra paper/):
|
||||
python3 diagnose.py eksempel_domstol.json
|
||||
|
|
@ -17,9 +17,11 @@ strømmer i saker per måned, beholdninger i saker, tider i måneder:
|
|||
"restanse_for_12_mnd": 400, # valgfritt: gir observert vekst i restansen
|
||||
"restanse_maal": 300, # valgfritt: akseptabel restanse (saker) i stedet for maal_tid;
|
||||
# oppgis begge, brukes restanse_maal
|
||||
"skjult_avgang": 0} # valgfritt: saker per måned som avsluttes uten behandling og
|
||||
"skjult_avgang": 0, # valgfritt: saker per måned som avsluttes uten behandling og
|
||||
# som er regnet med i kapasitet (f.eks. henlagt pga. kapasitet);
|
||||
# trekkes fra kapasiteten når utnyttelse og behov regnes ut
|
||||
"innstromning_for_okning": 100} # valgfritt, brukes bare av forlop.py: saker inn per måned
|
||||
# før strømmen begynte å øke
|
||||
]}
|
||||
|
||||
Verktøyet følger trinn (c)-(e) i metoden (notatet, avsnitt 7.7 og 9.4-9.5):
|
||||
|
|
@ -29,7 +31,7 @@ Verktøyet følger trinn (c)-(e) i metoden (notatet, avsnitt 7.7 og 9.4-9.5):
|
|||
(e) dimensjoner kapasiteten etter prognosen for strømmen, ikke etter køen, med
|
||||
den koordinerte regelen fra sim.py (simulate_endo, policy="koord"):
|
||||
Z_ny = (xi_min * F_prognose + Q_overskudd / T_avvikling) / ups_star.
|
||||
Forløpet over tid (simulering med egne tall) er ikke med i denne versjonen.
|
||||
Forløpet over tid og stabilitetssjekken ligger i forlop.py, som leser samme fil.
|
||||
Verktøyet regner ikke med kapasitet som er bevilget, men ennå ikke virker: oppgi
|
||||
kapasiteten som virker nå, og trekk det som allerede er bevilget, fra behovet.
|
||||
Kolonnen «Saksbeh.tid» er restanse delt på kapasitet, ikke målt saksbehandlingstid.
|
||||
|
|
@ -47,7 +49,8 @@ T_CLEAR = 24.0 # restansen avvikles over denne tiden, mnd (ENDO["T_clear"])
|
|||
L1, L2 = 6.0, 24.0 # bestilt kapasitet virker lineært fra L1 til L2 mnd etter vedtak
|
||||
|
||||
TALLFELT = ("innstromning", "kapasitet", "restanse", "maal_tid", "prognose_innstromning",
|
||||
"maks_andel_utenom", "restanse_for_12_mnd", "restanse_maal", "skjult_avgang")
|
||||
"maks_andel_utenom", "restanse_for_12_mnd", "restanse_maal", "skjult_avgang",
|
||||
"innstromning_for_okning")
|
||||
|
||||
|
||||
def tall(x, d=2):
|
||||
|
|
@ -75,6 +78,8 @@ def sjekk(data):
|
|||
verdi = ledd[felt]
|
||||
vist = json.dumps(verdi, ensure_ascii=False)
|
||||
try:
|
||||
if isinstance(verdi, bool):
|
||||
raise TypeError
|
||||
x = float(verdi)
|
||||
except (TypeError, ValueError):
|
||||
sys.exit(f'Feil i inndata, {hvor}: "{felt}" må være et tall '
|
||||
|
|
@ -111,9 +116,19 @@ def analyser(ledd):
|
|||
vekst=vekst, vekst_kilde=vekst_kilde, skjult=skjult,
|
||||
Fp=float(ledd.get("prognose_innstromning", F)),
|
||||
har_prognose="prognose_innstromning" in ledd,
|
||||
F_for=float(ledd["innstromning_for_okning"]) if "innstromning_for_okning" in ledd else None,
|
||||
andel=float(ledd.get("maks_andel_utenom", 0.0)))
|
||||
|
||||
|
||||
def skranke(rader):
|
||||
"""Trinn (c): høyest utnyttelse, deretter raskest voksende restanse relativt til kapasiteten."""
|
||||
return max(rader, key=lambda a: (round(a["ups"], 2), a["vekst"] / a["Z"]))
|
||||
|
||||
|
||||
def binder(b):
|
||||
return b["ups"] > UPS_HI or b["vekst"] > 0
|
||||
|
||||
|
||||
def tiltak(a):
|
||||
"""Trinn (d) og (e) for det bindende leddet."""
|
||||
ut = []
|
||||
|
|
@ -178,10 +193,9 @@ def main(fil):
|
|||
f"{tall(a['vekst'], 0):>19}")
|
||||
if any(a["tid"] is not None for a in rader):
|
||||
print("(Saksbeh.tid er restanse delt på kapasitet, ikke målt saksbehandlingstid.)")
|
||||
# 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"]))
|
||||
b = skranke(rader)
|
||||
print()
|
||||
if b["ups"] <= UPS_HI and b["vekst"] <= 0:
|
||||
if not binder(b):
|
||||
vokser = [a["navn"] for a in rader if a["vekst"] > 0]
|
||||
if vokser:
|
||||
print(f"Ingen ledd er over full utnyttelse, men restansen vokser i: {', '.join(vokser)}. "
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"merknad": "Stiliserte tall, ikke norske. Domstolen er modellens tall i måned 24 i avsnitt 9.5 (sim.py, koordinert regel) ganget med 1000: prognose for innstrømningen 0,13432, kapasitet 0,10/0,95, restanse over målet 0,27316. Restansen 588,95 er dette overskuddet pluss 3 x kapasiteten; modellens kø i samme måned er 564,4, fordi sim.py regner målet som (3 - m_D) x kapasiteten. De andre leddene er satt under full utnyttelse for å vise diagnosen.",
|
||||
"merknad": "Stiliserte tall, ikke norske. Domstolen er modellens tall i måned 24 i avsnitt 9.5 (sim.py, koordinert regel) ganget med 1000: prognose for innstrømningen 0,13432, kapasitet 0,10/0,95, restanse over målet 0,27316. Restansen 588,95 er dette overskuddet pluss 3 x kapasiteten; modellens kø i samme måned er 564,4, fordi sim.py regner målet som (3 - m_D) x kapasiteten. De andre leddene er satt under full utnyttelse for å vise diagnosen. Styringen er regime A i notatet og brukes bare av forlop.py.",
|
||||
"styring": {"forsinkelse": 3, "reaktivitet": 3},
|
||||
"ledd": [
|
||||
{"navn": "Politi", "innstromning": 150, "kapasitet": 175, "restanse": 300, "maal_tid": 2},
|
||||
{"navn": "Påtale", "innstromning": 140, "kapasitet": 160, "restanse": 200, "maal_tid": 1.5, "maks_andel_utenom": 0.1},
|
||||
|
|
|
|||
409
paper/forlop.py
Normal file
409
paper/forlop.py
Normal file
|
|
@ -0,0 +1,409 @@
|
|||
"""Forløp over tid og stabilitetssjekk for leddet som binder, fra egne tall.
|
||||
|
||||
Bruk (fra paper/, med samme Python-miljø som sim.py):
|
||||
../.venv/bin/python forlop.py eksempel_domstol.json
|
||||
../.venv/bin/python forlop.py norge_2025.json "Tingrett, meddomsrett" # velg ledd selv
|
||||
|
||||
Leser samme fil som diagnose.py. To ting kan legges til:
|
||||
|
||||
{"styring": {"forsinkelse": 3, # måneder fra kriminalitetsbildet endrer seg til innsatsen
|
||||
# er endret (statistikk + beslutning + iverksetting)
|
||||
"reaktivitet": 3}, # prosent mer innsats per prosent avvik fra målet
|
||||
"ledd": [{..., "innstromning_for_okning": 100}]} # saker inn per måned før strømmen økte
|
||||
|
||||
Uten "styring" brukes regime A i notatet (3 og 3). Uten "innstromning_for_okning"
|
||||
antas leddet 95 % utnyttet før økningen, som i notatet.
|
||||
|
||||
Hva skriptet gjør:
|
||||
1. Stabilitetssjekk: ligger forsinkelsen under den kritiske forsinkelsen for denne
|
||||
reaktiviteten (notatet, proposisjon 1 og avsnitt 9.3; critical_delay i sim.py)?
|
||||
2. Forløp: leddet som binder, legges inn som modellens ledd med kø (domstolen i
|
||||
avsnitt 9.5), og den koordinerte regelen kjøres måned for måned
|
||||
(simulate_endo, policy="koord"). Størrelsen på økningen og tidspunktet den
|
||||
begynte, settes slik at regelens første budsjettvedtak ser tallene i fila:
|
||||
prognosen for strømmen og restansen over målet. «Nå» er dette vedtaket.
|
||||
3. Resten av kjeden: politiets innsats, andelen ledet utenom, og hva flere avgjorte
|
||||
saker betyr for leddene som står etter i fila.
|
||||
|
||||
Kapasitet, restanse og saksbehandlingstid følger tallene i fila. Kriminalitet,
|
||||
tillit og politiets innsats følger modellens stiliserte parametere (tabell 3 i
|
||||
notatet), som ikke er kalibrert på norske data.
|
||||
|
||||
Bruker sim.py uendret og skriver ingenting til paper/data/.
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
from scipy.optimize import brentq
|
||||
|
||||
import diagnose
|
||||
from diagnose import tall
|
||||
from sim import (CH0, DT, ENDO, P, UPS0, VARSIGMA, Omega, critical_delay, predict_F,
|
||||
simulate_endo)
|
||||
|
||||
J0 = P["gamma"] * P["Lam0"] * P["T0"] # modellens strøm før økningen (0,10 per måned)
|
||||
NAA = 24.0 # modellmåneden for første budsjettvedtak etter økningen
|
||||
OBS = NAA - ENDO["lagD"] # vedtaket bygger på tall som er lagD måneder gamle
|
||||
SLUTT = 180.0 # som tabell 7 og 10 i notatet
|
||||
LAM_MYK = P["Lam0"] - 0.05 # myk grense for tillit (0,75), som i mpc_step
|
||||
UPS_FOR_MIN = ENDO["ups_lo"] + 0.01 # under dette bygger modellens regel ned kapasitet før økningen
|
||||
UPS_FOR_MAKS = 0.98 # over dette bestiller den kapasitet før økningen
|
||||
RADER = (-3, 0, 6, 12, 18, 24, 36, 48, 60, 72, 96, 120, 156)
|
||||
|
||||
|
||||
def idx(mnd):
|
||||
return int(round(mnd / DT))
|
||||
|
||||
|
||||
def les_styring(data):
|
||||
s = data.get("styring", {})
|
||||
if not isinstance(s, dict):
|
||||
sys.exit('Feil i inndata: "styring" må være et objekt med "forsinkelse" og "reaktivitet".')
|
||||
ut = {}
|
||||
for felt, standard, maks in (("forsinkelse", 3.0, 60.0), ("reaktivitet", 3.0, 50.0)):
|
||||
verdi = s.get(felt, standard)
|
||||
try:
|
||||
x = float("nan") if isinstance(verdi, bool) else float(verdi)
|
||||
except (TypeError, ValueError):
|
||||
x = float("nan")
|
||||
if not 0 < x <= maks:
|
||||
sys.exit(f'Feil i inndata, styring: "{felt}" må være et tall større enn 0 og høyst {maks:.0f}, '
|
||||
f"ikke {json.dumps(verdi, ensure_ascii=False)}.")
|
||||
ut[felt] = x
|
||||
ut["oppgitt"] = "styring" in data
|
||||
return ut
|
||||
|
||||
|
||||
def stabilitet(tau, kp):
|
||||
"""Lineær stabilitet i referanselikevekten: x' = -a x - b x(t - tau)."""
|
||||
c = P["gamma"] * P["Lam0"] # fjerningsrate per enhet innsats (0,10 per måned)
|
||||
a = c * P["T0"] + P["delta"]
|
||||
b = c * kp
|
||||
tc = critical_delay(a, b)
|
||||
print("Stabilitetssjekk: tåler styringen sin egen forsinkelse?\n")
|
||||
print(f"Forsinkelse {tall(tau, 1)} mnd, reaktivitet {tall(kp, 1)} "
|
||||
f"(10 % over målet gir {tall(kp * 10, 0)} % mer innsats).")
|
||||
if math.isinf(tc):
|
||||
print("Stabil uansett forsinkelse: reaksjonen er svakere enn systemets egen demping "
|
||||
f"(reaktivitet høyst {tall(a / c, 1)}).")
|
||||
return True
|
||||
stabil = tau < tc
|
||||
print(f"Kritisk forsinkelse: {tall(tc, 1)} mnd. " +
|
||||
(f"Forsinkelsen er kortere, så styringen er stabil (margin {tall(tc - tau, 1)} mnd)." if stabil else
|
||||
"Forsinkelsen er lengre, så styringen skaper svingninger selv: satsinger og kutt avløser "
|
||||
f"hverandre. Ved stabilitetsgrensen er perioden om lag "
|
||||
f"{tall(2 * math.pi / math.sqrt(b * b - a * a), 0)} mnd."))
|
||||
if critical_delay(a, c * 50.0) < tau:
|
||||
kp_maks = brentq(lambda k: critical_delay(a, c * k) - tau, a / c + 1e-6, 50.0)
|
||||
print(f"Med denne forsinkelsen er største stabile reaktivitet {tall(kp_maks, 1)}; "
|
||||
f"tommelfingerregelen gir under {tall(math.pi / (2 * c * tau), 1)}.")
|
||||
if not stabil:
|
||||
print(f"To veier ut: demp reaksjonen, eller kort forsinkelsen ned til under {tall(tc, 1)} mnd.")
|
||||
print("(Regnet med modellens stiliserte tall: innsatsen fjerner 10 % av kriminaliteten per måned, "
|
||||
"og 2 % faller fra av seg selv.)")
|
||||
return stabil
|
||||
|
||||
|
||||
def modell(b):
|
||||
"""Skaler leddet til modellens enheter. Returnerer skala, kjede, regelparametere og målene
|
||||
regelens første vedtak skal se (prognose og restanse over målet, i modellens enheter)."""
|
||||
Z_u = b["Z"]
|
||||
antatt = b["F_for"] is None
|
||||
F_for = UPS0 * Z_u if antatt else b["F_for"]
|
||||
hva = (f"innstrømningen før økningen ({tall(F_for, 1)} saker/mnd"
|
||||
+ (f", antatt {tall(UPS0 * 100, 0)} % av kapasiteten)" if antatt else ")"))
|
||||
if not UPS_FOR_MIN * Z_u <= F_for <= UPS_FOR_MAKS * Z_u:
|
||||
sys.exit(f"Forløpet forutsetter et ledd som var i balanse før økningen: {hva} må ligge mellom "
|
||||
f"{tall(UPS_FOR_MIN * 100, 0)} og {tall(UPS_FOR_MAKS * 100, 0)} % av kapasiteten "
|
||||
f"({tall(Z_u, 1)}). Utenfor det ville modellens regel ha endret kapasiteten allerede før "
|
||||
"økningen.")
|
||||
if b["Fp"] <= F_for:
|
||||
sys.exit(f"Forløpet har ingenting å vise for {b['navn']}: prognosen ({tall(b['Fp'], 1)} saker/mnd) "
|
||||
f"er ikke høyere enn {hva}.")
|
||||
skala = F_for / J0 # saker per måned per modellenhet
|
||||
Z = Z_u / skala
|
||||
tau_ref = b["Q_ref"] / Z_u # målet som måneder av kapasiteten modellen bruker
|
||||
if tau_ref <= 0:
|
||||
sys.exit("Forløpet trenger et mål for saksbehandlingstid eller restanse som er større enn 0.")
|
||||
m = tau_ref - J0 / (Z * (1.0 / tau_ref + VARSIGMA)) # som MD i sim.py: gir tau_ref før økningen
|
||||
ch = dict(CH0, Z=Z, m=m)
|
||||
e = dict(xi_min=1.0 - b["andel"])
|
||||
return dict(skala=skala, ch=ch, e=e, F_for=F_for, antatt=antatt, tau_ref=tau_ref,
|
||||
F_maal=b["Fp"] / skala, Q_maal=max(b["Q"] - b["Q_ref"], 0.0) / skala)
|
||||
|
||||
|
||||
def sett_ved_vedtak(okning, steg, mo, tau, kp, H=ENDO["H_pred"]):
|
||||
"""Det den koordinerte regelen ser ved første vedtak (sim.py, simulate_endo, budsjettsløyfen):
|
||||
prognosen F1 og restansen over målet, begge fra tilstanden lagD måneder før."""
|
||||
p = dict(P, shock=okning, t_shock=(steg - 0.5) * DT)
|
||||
r = simulate_endo("koord", tau=tau, p=p, ch=mo["ch"], e=mo["e"], kp=kp, tend=OBS)
|
||||
i = idx(OBS)
|
||||
F1 = predict_F(r["K"][i], r["U"][i], r["L"][i], r["Q"][i], Omega(r["t"][i], p), p, mo["ch"],
|
||||
r["tau_ref"], H, kp, idx(tau))
|
||||
Q_exc = max(r["Q"][i] - (r["tau_ref"] - mo["ch"]["m"]) * mo["ch"]["Z"], 0.0)
|
||||
return F1, Q_exc
|
||||
|
||||
|
||||
def kalibrer(mo, tau, kp):
|
||||
"""Finn økningen i pådraget og tidssteget den begynner, slik at første vedtak ser F_maal og Q_maal.
|
||||
Økningen må begynne etter at budsjettet i måned 12 har sett sine tall, og før tallene til vedtaket
|
||||
i måned 24 er tatt ut. Returnerer (økning, steg, kant), der kant er "tidlig" eller "sent" når
|
||||
restansen ikke kan treffes innenfor det vinduet."""
|
||||
def okning_for(steg):
|
||||
f = lambda x: sett_ved_vedtak(x, steg, mo, tau, kp)[0] - mo["F_maal"]
|
||||
lav, hoy = 1.0001, 3.0
|
||||
if f(lav) > 0:
|
||||
sys.exit("Forløpet har ingenting å vise: prognosen ligger for nær innstrømningen før økningen.")
|
||||
if f(hoy) < 0:
|
||||
sys.exit("Forløpet dekker ikke disse tallene: prognosen ligger for høyt over innstrømningen før "
|
||||
f"økningen ({tall(mo['F_maal'] / J0, 1)} ganger; modellen rekker om lag 3).")
|
||||
return brentq(f, lav, hoy, xtol=1e-6)
|
||||
|
||||
def avvik(steg):
|
||||
x = okning_for(steg)
|
||||
return x, sett_ved_vedtak(x, steg, mo, tau, kp)[1] - mo["Q_maal"]
|
||||
|
||||
tidlig, sent = idx(NAA - 12.0 - ENDO["lagD"]) + 1, idx(OBS) - 1
|
||||
x_t, g_t = avvik(tidlig)
|
||||
if g_t <= 0: # selv den lengste forhistorien gir for liten restanse
|
||||
return x_t, tidlig, "tidlig"
|
||||
x_s, g_s = avvik(sent)
|
||||
if g_s >= 0: # selv den korteste gir for stor
|
||||
return x_s, sent, "sent"
|
||||
while sent - tidlig > 1:
|
||||
midt = (tidlig + sent) // 2
|
||||
x_m, g_m = avvik(midt)
|
||||
if g_m > 0:
|
||||
tidlig, x_t, g_t = midt, x_m, g_m
|
||||
else:
|
||||
sent, x_s, g_s = midt, x_m, g_m
|
||||
return (x_t, tidlig, None) if abs(g_t) <= abs(g_s) else (x_s, sent, None)
|
||||
|
||||
|
||||
def naar(mnd):
|
||||
if mnd == 0:
|
||||
return "nå"
|
||||
return f"om {mnd:.0f} mnd" if mnd > 0 else f"for {-mnd:.0f} mnd siden"
|
||||
|
||||
|
||||
def vedtak(r, mo):
|
||||
"""Budsjettvedtakene i forløpet, fra loggen til simulate_endo. Samme nedbygging år etter år
|
||||
vises én gang."""
|
||||
ut = []
|
||||
for linje in r["log"]:
|
||||
t = re.match(r"mnd (\d+): bestill \+(\d+) %", linje)
|
||||
if t:
|
||||
ut.append((float(t.group(1)) - NAA, f"bestill {t.group(2)} % mer kapasitet", int(t.group(2))))
|
||||
continue
|
||||
t = re.match(r"mnd (\d+): bygg ned mot ([\d.]+)", linje)
|
||||
if not t:
|
||||
sys.exit(f"Kjenner ikke igjen vedtaket «{linje}» fra sim.py; forlop.py må oppdateres.")
|
||||
tekst = ("bygg ned ved naturlig avgang mot om lag "
|
||||
f"{tall(float(t.group(2)) * mo['ch']['Z'] * mo['skala'], 0)} saker/mnd")
|
||||
if not (ut and ut[-1][1] == tekst):
|
||||
ut.append((float(t.group(1)) - NAA, tekst, None))
|
||||
return ut
|
||||
|
||||
|
||||
def nokkeltall(r, Z0, fra):
|
||||
t = r["t"]
|
||||
return dict(tid=r["tauD"].max(), tillit=r["L"].min(), krim=r["K"][t >= fra].mean(),
|
||||
kap=r["Z"].max() / Z0 - 1.0,
|
||||
kapaar=np.trapezoid(np.maximum(r["Z"] - Z0, 0) / Z0, t) / 12)
|
||||
|
||||
|
||||
def ikke_regnet(grunn):
|
||||
print(f"Forløp: ikke regnet. {grunn}")
|
||||
|
||||
|
||||
def main(fil, valgt=None):
|
||||
data = diagnose.les(fil)
|
||||
st = les_styring(data)
|
||||
tau, kp = st["forsinkelse"], st["reaktivitet"]
|
||||
rader = [diagnose.analyser(ledd) for ledd in data["ledd"]]
|
||||
if valgt is None:
|
||||
b = diagnose.skranke(rader)
|
||||
else:
|
||||
treff = [a for a in rader if a["navn"] == valgt]
|
||||
if not treff:
|
||||
sys.exit(f'Fant ikke leddet "{valgt}". Leddene i fila: ' + ", ".join(a["navn"] for a in rader) + ".")
|
||||
b = treff[0]
|
||||
|
||||
stabil = stabilitet(tau, kp)
|
||||
if not st["oppgitt"]:
|
||||
print('(Fila har ikke "styring"; forsinkelse 3 mnd og reaktivitet 3 er regime A i notatet.)')
|
||||
print()
|
||||
if valgt is None and not diagnose.binder(b):
|
||||
print(f"Forløp: ingen ledd binder (høyeste utnyttelse er {tall(b['ups'])}, {b['navn']}), "
|
||||
"så det er ikke noe tiltak å følge over tid.")
|
||||
return
|
||||
if not stabil:
|
||||
ikke_regnet("Når styringen selv skaper svingninger, finnes det ingen ny balanse å dimensjonere etter, "
|
||||
"og prognosen regelen bruker, blir tilfeldig. Rett styringen først (trinn (e) i metoden "
|
||||
"krever det), og kjør på nytt.")
|
||||
return
|
||||
|
||||
mo = modell(b)
|
||||
okning, steg, kant = kalibrer(mo, tau, kp)
|
||||
s, ch = mo["skala"], mo["ch"]
|
||||
Z0 = ch["Z"]
|
||||
F1, Q_sett = sett_ved_vedtak(okning, steg, mo, tau, kp)
|
||||
F2, _ = sett_ved_vedtak(okning, steg, mo, tau, kp, H=2 * ENDO["H_pred"])
|
||||
if abs(F2 - F1) > 0.01 * F1:
|
||||
ikke_regnet("Stabilitetssjekken er bestått, men prognosen for strømmen har ikke falt til ro innen "
|
||||
f"horisonten på {tall(ENDO['H_pred'], 0)} mnd ({tall(F1 * s, 1)} saker/mnd, mot "
|
||||
f"{tall(F2 * s, 1)} med dobbel horisont): styringen er for nær grensen eller for treg. Da "
|
||||
"finnes det ikke ett tall å dimensjonere etter. Prøv med kortere forsinkelse.")
|
||||
return
|
||||
Q_u = max(b["Q"] - b["Q_ref"], 0.0)
|
||||
behov = ((1.0 - b["andel"]) * b["Fp"] + Q_u / diagnose.T_CLEAR) / diagnose.UPS_STAR
|
||||
behov_modell = (mo["e"]["xi_min"] * F1 + Q_sett / ENDO["T_clear"]) / ENDO["ups_star"] * s
|
||||
|
||||
def bestilling(x): # som regelen: ingen bestilling når behovet før marginen er dekket
|
||||
return x / b["Z"] - 1.0 if x * ENDO["ups_star"] > ENDO["ups_hi"] * b["Z"] else 0.0
|
||||
if abs(bestilling(behov_modell) - bestilling(behov)) > 0.02:
|
||||
ikke_regnet(f"Modellen bygger opp restansen selv, og den treffer ikke tallene i fila ved første vedtak: "
|
||||
f"{tall(Q_sett * s, 0)} saker over målet mot {tall(Q_u, 0)} i fila. Første vedtak ville blitt "
|
||||
f"{tall(bestilling(behov_modell) * 100, 0)} % mer kapasitet, mens diagnose.py regner "
|
||||
f"{tall(bestilling(behov) * 100, 0)} %. Et forløp fra et annet utgangspunkt enn ditt "
|
||||
"ville villede. Bruk diagnosen; denne versjonen kan ikke starte i en restanse som har "
|
||||
"vokst lenge.")
|
||||
return
|
||||
|
||||
p = dict(P, shock=okning, t_shock=(steg - 0.5) * DT)
|
||||
r = simulate_endo("koord", tau=tau, p=p, ch=ch, e=mo["e"], kp=kp, tend=SLUTT)
|
||||
r0 = simulate_endo("sched", tau=tau, p=p, ch=ch, e=mo["e"], kp=kp, tend=SLUTT)
|
||||
alle = vedtak(r, mo)
|
||||
if any(mnd < 0 for mnd, _, _ in alle):
|
||||
sys.exit("Forløpet kan ikke legges med disse tallene: modellens regel gjør vedtak før økningen er "
|
||||
"observert. Kontroller kapasiteten og innstrømningen før økningen.")
|
||||
|
||||
print(f"Forløp over tid for {'leddet som binder' if valgt is None else 'leddet du valgte'}: {b['navn']}\n")
|
||||
print("Slik er tallene lagt inn i modellen:")
|
||||
print("- Leddet regnes som modellens ledd med kø: det tar imot sakene politiets innsats gir "
|
||||
"(domstolen i notatet).")
|
||||
print(f"- Fra fila: kapasitet {tall(b['Z'], 1)} saker/mnd"
|
||||
+ (" (etter skjult avgang)" if b["skjult"] > 0 else "")
|
||||
+ f", prognose {tall(b['Fp'], 1)} saker/mnd"
|
||||
+ ("" if b["har_prognose"] else " (innstrømningen nå; ingen prognose oppgitt)")
|
||||
+ f", restanse {tall(b['Q'], 0)} ({tall(Q_u, 0)} over målet på {tall(b['Q_ref'], 0)}), "
|
||||
f"høyst {tall(b['andel'] * 100, 0)} % ledet utenom.")
|
||||
print(f"- Før økningen: {tall(mo['F_for'], 1)} saker/mnd inn, utnyttelse {tall(mo['F_for'] / b['Z'])}"
|
||||
+ (' (antatt; oppgi "innstromning_for_okning" for å endre).' if mo["antatt"] else " (oppgitt)."))
|
||||
print(f"- Modellen gjenskaper tallene med en varig økning i det strukturelle pådraget på "
|
||||
f"{tall((okning - 1) * 100, 0)} % som begynte for {tall(NAA - steg * DT, 0)} mnd siden"
|
||||
+ ({"sent": " (det seneste modellen tillater)", "tidlig": " (det tidligste modellen tillater)"}[kant]
|
||||
if kant else "") + ".")
|
||||
print(f"- «Nå» er første budsjettvedtak etter økningen. Tallene det bygger på, er "
|
||||
f"{tall(ENDO['lagD'], 0)} mnd gamle, som tallene i fila.")
|
||||
if b["andel"] > 0 and r["xi"][idx(NAA)] < 0.995:
|
||||
print(f"- I modellens forhistorie har den koordinerte regelen alt begynt å lede saker utenom "
|
||||
f"({tall((1 - r['xi'][idx(NAA)]) * 100, 0)} % nå).")
|
||||
avvik = abs(Q_sett * s - Q_u) > max(0.02 * b["Q_ref"], 1.0)
|
||||
if avvik:
|
||||
print(f"- Avvik: modellen starter med {tall(Q_sett * s, 0)} saker over målet, fila har {tall(Q_u, 0)}. "
|
||||
+ ("Restansen i fila er under målet. Modellen kan ikke starte under målet, og den har noe "
|
||||
"restanse over målet allerede før økningen." if b["Q"] < b["Q_ref"] else
|
||||
"Modellen rekker ikke å bygge opp like stor restanse før første vedtak." if Q_sett * s < Q_u else
|
||||
"Modellen får ikke restansen lavere før første vedtak."))
|
||||
|
||||
print("\nVedtak i forløpet (budsjettet vedtar én gang i året; bestilt kapasitet virker gradvis "
|
||||
f"fra {tall(ENDO['L1'], 0)} til {tall(ENDO['L2'], 0)} mnd etter vedtak):")
|
||||
for mnd, tekst, _ in alle:
|
||||
print(f"- {naar(mnd)}: {tekst}")
|
||||
if not alle:
|
||||
print("- ingen: regelen bestiller ikke kapasitet med disse tallene.")
|
||||
forste = next((pst for mnd, _, pst in alle if mnd == 0 and pst is not None), None)
|
||||
diag = round(bestilling(behov) * 100)
|
||||
if forste is not None and forste == diag:
|
||||
print(f" Første vedtak er det samme som diagnose.py regner ut ({diag} %).")
|
||||
elif forste is not None:
|
||||
print(f" diagnose.py regner {diag} % for første vedtak; modellens restanse ved start ligger litt unna "
|
||||
"fila.")
|
||||
if len([1 for _, _, pst in alle if pst is not None]) > 1:
|
||||
print(" Senere bestillinger kommer fordi restansen har vokst, eller prognosen er endret, mens den "
|
||||
"første kapasiteten bygges opp.")
|
||||
|
||||
print(f"\n{'Mnd fra nå':>10}{'Kapasitet':>11}{'Utenom':>8}{'Restanse':>10}{'Saksbeh.tid':>13}"
|
||||
f"{'Politiinnsats':>15}{'Kriminalitet':>14}{'Tillit':>8}")
|
||||
for mnd in RADER:
|
||||
k = idx(mnd + NAA)
|
||||
print(f"{mnd:>10}{tall(r['Z'][k] * s, 1):>11}{tall((1 - r['xi'][k]) * 100, 0) + ' %':>8}"
|
||||
f"{tall(r['tauD'][k] * r['Z'][k] * s, 0):>10}{tall(r['tauD'][k], 1):>13}"
|
||||
f"{tall(r['T'][k]):>15}{tall(r['K'][k]):>14}{tall(r['L'][k]):>8}")
|
||||
print("(Kapasitet i saker/mnd, restanse i saker, saksbehandlingstid i mnd = restanse delt på kapasitet. "
|
||||
"Politiinnsats og kriminalitet: 1 = nivået før økningen. Tillit: 0,80 før økningen.)")
|
||||
|
||||
fra = steg * DT
|
||||
med, uten = nokkeltall(r, Z0, fra), nokkeltall(r0, Z0, fra)
|
||||
print(f"\nMed og uten tiltak i kjeden, fra økningen begynte til {tall(SLUTT - NAA, 0)} mnd fra nå:\n")
|
||||
print(f"{'':<34}{'Med tiltakene':>14}{'Uten':>10}")
|
||||
print(f"{'Lengste saksbehandlingstid (mnd)':<34}{tall(med['tid'], 1):>14}{tall(uten['tid'], 1):>10}")
|
||||
print(f"{'Laveste tillit':<34}{tall(med['tillit']):>14}{tall(uten['tillit']):>10}")
|
||||
print(f"{'Kriminalitet, gjennomsnitt':<34}{tall(med['krim']):>14}{tall(uten['krim']):>10}")
|
||||
print(f"{'Høyeste kapasitet':<34}{'+' + tall(med['kap'] * 100, 0) + ' %':>14}{'+0 %':>10}")
|
||||
print(f"{'Kapasitetsår over utgangsnivået':<34}{tall(med['kapaar'], 1):>14}{tall(0, 1):>10}")
|
||||
if med["tillit"] < LAM_MYK:
|
||||
print(f"Tilliten kommer under modellens myke grense ({tall(LAM_MYK)}). Trinn (f) peker da på "
|
||||
"prosessuell kvalitet i tillegg.")
|
||||
sent = r["t"] > SLUTT - 60.0
|
||||
spenn = {n: (float(r[n][sent].min()), float(r[n][sent].max())) for n in ("K", "tauD", "Z")}
|
||||
i_ro = (spenn["K"][1] - spenn["K"][0] <= 0.01
|
||||
and spenn["tauD"][1] - spenn["tauD"][0] <= 0.05 * mo["tau_ref"]
|
||||
and spenn["Z"][1] - spenn["Z"][0] <= 0.01 * Z0)
|
||||
if i_ro:
|
||||
print(f"Forløpet faller til ro: kriminaliteten ender på {tall(r['K'][-1])} og saksbehandlingstiden på "
|
||||
f"{tall(r['tauD'][-1], 1)} mnd.")
|
||||
ups_slutt = r["JD"][-1] / r["Z"][-1]
|
||||
if ups_slutt < ENDO["ups_star"] - 0.03:
|
||||
print(f"Kapasiteten ender høyere enn strømmen krever (utnyttelse {tall(ups_slutt)} mot målet "
|
||||
f"{tall(ENDO['ups_star'])}): det som ble bestilt for å avvikle restansen, blir stående, fordi "
|
||||
f"regelen først bygger ned under {tall(ENDO['ups_lo'])}.")
|
||||
else:
|
||||
print("Forløpet har ikke falt til ro ved slutten. De siste fem årene ligger kriminaliteten mellom "
|
||||
f"{tall(spenn['K'][0])} og {tall(spenn['K'][1])}, saksbehandlingstiden mellom "
|
||||
f"{tall(spenn['tauD'][0], 1)} og {tall(spenn['tauD'][1], 1)} mnd og kapasiteten mellom "
|
||||
f"{tall(spenn['Z'][0] * s, 1)} og {tall(spenn['Z'][1] * s, 1)} saker/mnd. Sluttallene over er da "
|
||||
"ikke en ny balanse.")
|
||||
|
||||
print("\nResten av kjeden:")
|
||||
print(f"- Politiet: innsatsen går fra 1,00 til høyst {tall(r['T'].max())} og ender på {tall(r['T'][-1])}. "
|
||||
"Å bremse politiet til leddets takt er ikke et tiltak (diagnose.py).")
|
||||
if b["andel"] > 0:
|
||||
utenom = 1.0 - r["xi"]
|
||||
print(f"- Saker ledet utenom {b['navn']}: høyst {tall(utenom.max() * 100, 0)} % "
|
||||
f"({naar(round(r['t'][int(np.argmax(utenom))] - NAA))}) og {tall(utenom[-1] * 100, 0)} % til "
|
||||
"slutt. Hvor de sakene havner, er ikke regnet.")
|
||||
R_data = r["RD"][idx(OBS)]
|
||||
topp, slutt = r["RD"].max() / R_data, r["RD"][-1] / R_data
|
||||
print(f"- Ut av {b['navn']}: i modellen avgjør leddet på det meste {tall((topp - 1) * 100, 0)} % flere saker "
|
||||
f"enn på tidspunktet tallene i fila gjelder ({naar(round(r['t'][int(np.argmax(r['RD']))] - NAA))}), "
|
||||
f"og {tall((slutt - 1) * 100, 0)} % flere til slutt.")
|
||||
plass = next(i for i, a in enumerate(rader) if a is b)
|
||||
for a in rader[plass + 1:]:
|
||||
if max(topp, slutt) <= 1.005:
|
||||
print(f"- {a['navn']}: uendret innstrømning herfra.")
|
||||
continue
|
||||
ny_topp, ny_slutt = a["F"] * topp, a["F"] * slutt
|
||||
dom = ("Leddet binder allerede, og presset øker." if a["ups"] > diagnose.UPS_HI else
|
||||
"Skranken flytter seg dit." if ny_slutt > diagnose.UPS_HI * a["Z"] else
|
||||
"Leddet er over full utnyttelse mens restansen avvikles, ikke varig."
|
||||
if ny_topp > diagnose.UPS_HI * a["Z"] else "Leddet tar det unna.")
|
||||
print(f"- {a['navn']}: får leddet sakene sine gjennom {b['navn']}, går innstrømningen fra "
|
||||
f"{tall(a['F'], 1)} til om lag {tall(ny_topp, 1)} per mnd på det meste og {tall(ny_slutt, 1)} til "
|
||||
f"slutt, mot kapasitet {tall(a['Z'], 1)}: utnyttelse {tall(ny_topp / a['Z'])} og "
|
||||
f"{tall(ny_slutt / a['Z'])}. {dom}")
|
||||
if not rader[plass + 1:]:
|
||||
print(f"- Fila har ingen ledd etter {b['navn']}.")
|
||||
print("\nLes forløpet slik: kapasitet, restanse og saksbehandlingstid følger tallene i fila. Politiinnsats, "
|
||||
"kriminalitet og tillit følger modellens stiliserte parametere, som ikke er kalibrert på norske data. "
|
||||
"Rekkefølgen og retningen er poenget, ikke tallene. Bare leddet som binder, har kø i modellen.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) not in (2, 3):
|
||||
sys.exit('Bruk: python forlop.py <inndata.json> ["navn på ledd"]')
|
||||
main(*sys.argv[1:])
|
||||
Loading…
Add table
Add a link
Reference in a new issue