feat(diagnose): bottleneck diagnosis from own numbers, first version
paper/diagnose.py takes inflow, capacity, backlog and target time per
link and returns utilization, throughput time and backlog growth per
link, the binding link, the type of measure in Theory of Constraints
order (exploit, subordinate, elevate) and, for the binding link, the
capacity needed under the coordinated rule from sim.py, sized on the
forecast flow rather than the queue.
The example file holds the stylized month-24 values from the coordinated
run and reproduces section 9.5 (utilization 1.28, +19 % capacity);
checked against simulate_endo("koord"), which orders +19 % in month 24.
sim.py and paper/data/ are unchanged. Time paths and the stability check
for the police rule are not in this version.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
5d4557d912
commit
809dd4ccba
5 changed files with 156 additions and 3 deletions
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
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å.
|
||||
|
||||
## 2026-11-10 — PDF-ene bygd på nytt fra kilden
|
||||
|
||||
- Notatet (57 sider), sammendraget og lysarkene er bygd fra LaTeX-kilden i repoet og erstatter de første PDF-ene.
|
||||
|
|
|
|||
|
|
@ -11,12 +11,13 @@ 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`, `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`, `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
|
||||
- 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).
|
||||
- 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 57 pages, notat 2, karusell 16, no undefined references). Build in a scratch copy of `paper/` to keep aux files out of the repo.
|
||||
|
||||
|
|
|
|||
17
README.md
17
README.md
|
|
@ -31,6 +31,19 @@ cd paper
|
|||
|
||||
Skriptet bruker om lag to minutter. Det skriver ut nøkkeltallene som brukes i teksten, og skriver figurdataene til `paper/data/`. Med versjonene over blir alle 22 datafilene byte for byte like dem som ligger i repoet. Det kan du sjekke selv: `git status paper/data` skal ikke vise noen endringer etter kjøringen. Ingenting kjører denne sjekken automatisk.
|
||||
|
||||
## Finn flaskehalsen med egne tall
|
||||
|
||||
`paper/diagnose.py` går fra tall per ledd i kjeden til leddet som binder og tiltakstypen, etter trinn (c)–(e) i notatets metode. Du oppgir for hvert ledd innstrømning og kapasitet (saker per måned), restanse (saker) og målet for saksbehandlingstid (måneder). Hvis noen saker kan ledes til forenklede reaksjoner, oppgir du også hvor stor andel det kan være. Skriptet trenger bare Python 3.
|
||||
|
||||
```bash
|
||||
cd paper
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
|
@ -59,7 +72,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) og simuleringen `sim.py` |
|
||||
| `paper/` | LaTeX-kilden (`main.tex` er notatet, `notat.tex` sammendraget, `karusell.tex` lysarkene), simuleringen `sim.py` og flaskehalsdiagnosen `diagnose.py` med eksempelfil |
|
||||
| `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.
|
||||
|
|
@ -73,7 +86,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`, `requirements.txt` og figurdataene i `paper/data/`, er lisensiert under [MIT](LICENSE).
|
||||
- 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).
|
||||
|
||||
## Changelog
|
||||
See [CHANGELOG.md](CHANGELOG.md).
|
||||
|
|
|
|||
125
paper/diagnose.py
Normal file
125
paper/diagnose.py
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
"""Flaskehalsdiagnose for straffesakskjeden fra egne tall (første versjon).
|
||||
|
||||
Bruk (fra paper/):
|
||||
../.venv/bin/python diagnose.py eksempel_domstol.json
|
||||
|
||||
Inndata er en JSON-fil med ett objekt per ledd i kjeden, i rekkefølge. Alle
|
||||
strømmer i saker per måned, beholdninger i saker, tider i måneder:
|
||||
|
||||
{"ledd": [
|
||||
{"navn": "Domstol",
|
||||
"innstromning": 134, # saker inn per måned nå
|
||||
"kapasitet": 105, # saker ledd kan avgjøre per måned
|
||||
"restanse": 669, # saker i kø nå
|
||||
"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
|
||||
]}
|
||||
|
||||
Verktøyet følger trinn (c)-(e) i metoden (artikkelen, avsnitt 9.4-9.5):
|
||||
(c) finn skranken som binder: høyest utnyttelse, deretter raskest voksende restanse;
|
||||
(d) velg tiltakstype etter Theory of Constraints: utnytt, underordne, utvid;
|
||||
(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.
|
||||
Restansen over målet regnes som restanse - maal_tid * kapasitet (Littles lov).
|
||||
sim.py trekker i tillegg fra minste behandlingstid m_D; eksempelfila er satt
|
||||
slik at overskuddet blir det samme som i sim.py i måned 24.
|
||||
Bruker ingenting fra sim.py og endrer ikke paper/data/.
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
|
||||
UPS_STAR = 0.95 # målutnyttelse ved dimensjonering (ENDO["ups_star"] i sim.py)
|
||||
UPS_HI = 1.0 # bestill når utnyttelsen er over denne (ENDO["ups_hi"])
|
||||
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
|
||||
|
||||
|
||||
def tall(x, d=2):
|
||||
return f"{x:,.{d}f}".replace(",", " ").replace(".", ",")
|
||||
|
||||
|
||||
def analyser(ledd):
|
||||
F = float(ledd["innstromning"])
|
||||
Z = float(ledd["kapasitet"])
|
||||
Q = float(ledd["restanse"])
|
||||
tid_maal = float(ledd["maal_tid"])
|
||||
ups = F / Z
|
||||
tid = Q / Z # Littles lov: beholdning / avgangsrate
|
||||
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,
|
||||
vekst=vekst, vekst_kilde=vekst_kilde,
|
||||
Fp=float(ledd.get("prognose_innstromning", F)),
|
||||
andel=float(ledd.get("maks_andel_utenom", 0.0)))
|
||||
|
||||
|
||||
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)
|
||||
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"]
|
||||
ut.append(f"2. Led saker utenom: med {tall(a['andel'] * 100, 0)} % til forenklede reaksjoner "
|
||||
f"blir utnyttelsen {tall(ups_u)} (fra {tall(a['Fp'] / a['Z'])} med prognosen).")
|
||||
if ups_u <= UPS_HI and Q_exc == 0:
|
||||
ut.append(" Det rekker: kapasiteten trenger ikke utvides.")
|
||||
return ut
|
||||
else:
|
||||
ut.append("2. Led saker utenom: ikke oppgitt at noen andel kan ledes utenom dette leddet.")
|
||||
behov = (xi_min * a["Fp"] + Q_exc / T_CLEAR) / UPS_STAR
|
||||
if behov > UPS_HI * a["Z"]:
|
||||
okning = behov / a["Z"] - 1.0
|
||||
ut.append(f"3. Utvid: dimensjoner etter prognosen ({tall(a['Fp'], 1)} saker/mnd), ikke etter køen. "
|
||||
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.")
|
||||
else:
|
||||
ut.append("3. Utvid: ikke nødvendig med disse tallene.")
|
||||
return ut
|
||||
|
||||
|
||||
def main(fil):
|
||||
with open(fil, encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
rader = [analyser(l) for l in data["ledd"]]
|
||||
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}"
|
||||
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"]))
|
||||
print()
|
||||
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']}).")
|
||||
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. "
|
||||
"Når den løses, øker innstrømningen dit.")
|
||||
print("\nTrinn (d)-(e): tiltak etter Theory of Constraints, i denne rekkefølgen\n")
|
||||
for linje in tiltak(b):
|
||||
print(linje)
|
||||
print("\nÅ bremse leddene foran skranken til dens takt er ikke et tiltak: i modellen gir det det dårligste utfallet.")
|
||||
print("Kontroller så de rettsstatlige skrankene (trinn f): det er sakstyper, ikke prosentandeler, som flyttes.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 2:
|
||||
sys.exit("Bruk: python diagnose.py <inndata.json>")
|
||||
main(sys.argv[1])
|
||||
10
paper/eksempel_domstol.json
Normal file
10
paper/eksempel_domstol.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"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. De andre leddene er satt under full utnyttelse for å vise diagnosen.",
|
||||
"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},
|
||||
{"navn": "Domstol", "innstromning": 134.32, "kapasitet": 105.263, "restanse": 588.95, "maal_tid": 3,
|
||||
"prognose_innstromning": 134.32, "maks_andel_utenom": 0.2},
|
||||
{"navn": "Kriminalomsorg", "innstromning": 90, "kapasitet": 100, "restanse": 150, "maal_tid": 2}
|
||||
]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue