feat: model core, launcher, reproduction check and the first two skills
The core in modell/kjerne/ is an unmodified copy of sim.py, diagnose.py and forlop.py from den-dynamiske-rettsstaten @2badc25, pinned by sha256. kjor.py is the single entry point; kontroll.py reproduces the note's 22 data files and printed results. Skills: diagnose and forlop (Norwegian). Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
commit
464ad38b49
51 changed files with 11207 additions and 0 deletions
170
tests/test_kontroll.py
Normal file
170
tests/test_kontroll.py
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
# Copyright (c) 2026 Kjell Tore Guttormsen. MIT License, see LICENSE.
|
||||
"""kontroll.py: sammenligningene, dommene og nivået."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import kontroll
|
||||
import pytest
|
||||
from conftest import kjor
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# ---------------- sammenligningene ----------------
|
||||
|
||||
@pytest.mark.parametrize(("tall", "forventet"), [("19", 1.0), ("1,28", 0.01), ("7.21", 0.01),
|
||||
("0.00000", 1e-5), ("1.11e-15", 1e-17), ("-3.0E+02", 10.0)])
|
||||
def test_enhet_i_siste_siffer(tall: str, forventet: float) -> None:
|
||||
assert kontroll.enhet(tall) == pytest.approx(forventet)
|
||||
|
||||
|
||||
def test_tekst_lik() -> None:
|
||||
assert kontroll.sammenlign_tekst("a 1,23\n", "a 1,23\n").status == kontroll.LIK
|
||||
|
||||
|
||||
def test_tekst_innenfor_toleranse() -> None:
|
||||
resultat = kontroll.sammenlign_tekst("utnyttelse 1,23 og 7.21\n", "utnyttelse 1,24 og 7.20\n")
|
||||
assert resultat.status == kontroll.TOLERANSE
|
||||
assert resultat.avvik == pytest.approx(0.01)
|
||||
assert "-utnyttelse 1,23 og 7.21" in resultat.detaljer
|
||||
assert "+utnyttelse 1,24 og 7.20" in resultat.detaljer
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("fasit", "naa"), [("a 1,23\n", "a 1,25\n"), ("a 1,23\n", "b 1,23\n"),
|
||||
("a 1,23\n", "a 1,23\nb\n"), ("19 %\n", "21 %\n")])
|
||||
def test_tekst_endret(fasit: str, naa: str) -> None:
|
||||
resultat = kontroll.sammenlign_tekst(fasit, naa)
|
||||
assert resultat.status == kontroll.ENDRET
|
||||
assert resultat.detaljer
|
||||
|
||||
|
||||
DAT = "t K Q\n0.00000 1.00000 0.29100\n0.50000 1.10000 0.30000\n"
|
||||
|
||||
|
||||
def test_dat_lik() -> None:
|
||||
assert kontroll.sammenlign_dat(DAT, DAT).status == kontroll.LIK
|
||||
|
||||
|
||||
def test_dat_innenfor_toleranse() -> None:
|
||||
resultat = kontroll.sammenlign_dat(DAT, DAT.replace("1.10000", "1.10001"))
|
||||
assert resultat.status == kontroll.TOLERANSE
|
||||
assert resultat.detaljer == ["største absolutte avvik per kolonne: K 1,0e-05"]
|
||||
|
||||
|
||||
def test_dat_endret_viser_avvik_per_kolonne() -> None:
|
||||
resultat = kontroll.sammenlign_dat(DAT, DAT.replace("1.10000", "1.10030").replace("0.30000", "0.30200"))
|
||||
assert resultat.status == kontroll.ENDRET
|
||||
assert resultat.detaljer == ["største absolutte avvik per kolonne: K 3,0e-04, Q 2,0e-03"]
|
||||
assert resultat.avvik == pytest.approx(0.002)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("naa", ["t K X\n0.00000 1.00000 0.29100\n0.50000 1.10000 0.30000\n",
|
||||
"t K Q\n0.00000 1.00000 0.29100\n"])
|
||||
def test_dat_ulik_form(naa: str) -> None:
|
||||
assert kontroll.sammenlign_dat(DAT, naa).status == kontroll.ENDRET
|
||||
|
||||
|
||||
# ---------------- kontrollen som helhet ----------------
|
||||
|
||||
def test_rask_kontroll_av_pluginen(hjem: Path) -> None:
|
||||
prosess = kjor("kontroll", "--rask", hjem=hjem)
|
||||
assert prosess.returncode == 0, prosess.stdout + prosess.stderr
|
||||
assert "1. Kjernen: uendret fra notatet (3 filer," in prosess.stdout
|
||||
assert "2. diagnose: lik notatet for eksempel_domstol og norge_2025." in prosess.stdout
|
||||
assert "3. forlop: lik notatet for eksempel_domstol og norge_2025." in prosess.stdout
|
||||
assert "4. Full kjøring: hoppet over (--rask)." in prosess.stdout
|
||||
assert "Nivå (uten full kjøring, --rask): bit for bit som notatet." in prosess.stdout
|
||||
|
||||
|
||||
def test_kontroll_uten_numpy_gjor_det_den_kan(hjem: Path) -> None:
|
||||
prosess = kjor("kontroll", python=[sys.executable, "-S"], hjem=hjem)
|
||||
assert prosess.returncode == 3
|
||||
assert "2. diagnose: lik notatet" in prosess.stdout
|
||||
assert "3. forlop: ikke kjørt, numpy og scipy mangler." in prosess.stdout
|
||||
assert "4. Full kjøring: ikke kjørt, numpy og scipy mangler." in prosess.stdout
|
||||
assert "Nivå (bare det som er kjørt): bit for bit som notatet." in prosess.stdout
|
||||
|
||||
|
||||
def endre_diagnose(kopi: Path) -> None:
|
||||
"""Endre målutnyttelsen i kopiens diagnose.py fra 0,95 til 0,90."""
|
||||
fil = kopi / "modell" / "kjerne" / "diagnose.py"
|
||||
tekst = fil.read_text(encoding="utf-8")
|
||||
assert "UPS_STAR = 0.95" in tekst
|
||||
fil.write_text(tekst.replace("UPS_STAR = 0.95", "UPS_STAR = 0.90"), encoding="utf-8")
|
||||
|
||||
|
||||
def test_endret_kjerne_i_pluginen_er_en_feil(kopi: Path, hjem: Path) -> None:
|
||||
endre_diagnose(kopi)
|
||||
prosess = kjor("--modell", str(kopi), "kontroll", "--rask", hjem=hjem)
|
||||
assert prosess.returncode == 1
|
||||
assert "diagnose.py er endret" in prosess.stdout
|
||||
assert "2. diagnose: endret (se under)." in prosess.stdout
|
||||
assert "- Kapasitet: (0,80 x 134,3 + 273/24) / 0,95 = 125,1 saker/mnd, det vil si 19 % mer." in prosess.stdout
|
||||
assert "Nivå (uten full kjøring, --rask): endret: Kjernen, diagnose, forlop." in prosess.stdout
|
||||
assert "Pluginens modell skal regne som notatet, så dette er en feil." in prosess.stdout
|
||||
|
||||
|
||||
def test_endret_kjerne_i_arbeidskopi_er_en_opplysning(kopi: Path, hjem: Path) -> None:
|
||||
endre_diagnose(kopi)
|
||||
(kopi / "LES-MEG.md").write_text("Arbeidskopi\n", encoding="utf-8")
|
||||
prosess = kjor("--modell", str(kopi), "kontroll", "--rask", hjem=hjem)
|
||||
assert prosess.returncode == 1
|
||||
assert "(arbeidskopi)" in prosess.stdout
|
||||
assert "det er en opplysning, ikke en feil" in prosess.stdout
|
||||
assert "+ Kapasitet: (0,80 x 134,3 + 273/24) / 0,90" in prosess.stdout
|
||||
|
||||
|
||||
def test_manglende_fasit(kopi: Path, hjem: Path) -> None:
|
||||
(kopi / "fasit" / "diagnose_norge_2025.txt").unlink()
|
||||
prosess = kjor("--modell", str(kopi), "kontroll", "--rask", hjem=hjem)
|
||||
assert prosess.returncode == 1
|
||||
assert "norge_2025: fant ikke" in prosess.stdout
|
||||
|
||||
|
||||
def lag_utvidelser(kopi: Path, filer: dict[str, str]) -> None:
|
||||
"""Legg utvidelser i kopiens modell/utvidelser/ (og fjern dem som lå der)."""
|
||||
mappe = kopi / "modell" / "utvidelser"
|
||||
mappe.mkdir(exist_ok=True)
|
||||
for gammel in mappe.glob("*.py"):
|
||||
gammel.unlink()
|
||||
for navn, innhold in filer.items():
|
||||
(mappe / f"{navn}.py").write_text(innhold, encoding="utf-8")
|
||||
|
||||
|
||||
def test_utvidelse_som_gjenskaper_kjernen(kopi: Path, hjem: Path) -> None:
|
||||
lag_utvidelser(kopi, {
|
||||
"eksakt": "import sim\n\ndef grensetilfelle():\n return [('p3 med trappetrinn', 0.0)]\n",
|
||||
"_hjelper": "raise RuntimeError('filer som begynner med _ skal ikke kjøres')\n",
|
||||
})
|
||||
prosess = kjor("--modell", str(kopi), "kontroll", "--rask", hjem=hjem)
|
||||
assert prosess.returncode == 0, prosess.stdout + prosess.stderr
|
||||
assert "5. Utvidelser: grensetilfellene gjenskaper kjernen eksakt (eksakt)." in prosess.stdout
|
||||
|
||||
|
||||
def test_utvidelser_som_ikke_gjenskaper_kjernen(kopi: Path, hjem: Path) -> None:
|
||||
lag_utvidelser(kopi, {
|
||||
"avvik": "def grensetilfelle():\n return [('p3 med trappetrinn', 1e-12), ('p6', 0.0)]\n",
|
||||
"tom": "def grensetilfelle():\n return []\n",
|
||||
"odelagt": "import sim\nraise RuntimeError('i stykker')\n",
|
||||
})
|
||||
prosess = kjor("--modell", str(kopi), "kontroll", "--rask", hjem=hjem)
|
||||
assert prosess.returncode == 1, prosess.stdout + prosess.stderr
|
||||
assert "5. Utvidelser: grensetilfellet gjenskaper ikke kjernen eksakt (avvik, odelagt, tom; se under)." in (
|
||||
prosess.stdout)
|
||||
assert "avvik: p3 med trappetrinn: avvik 1,0e-12, skal være eksakt 0" in prosess.stdout
|
||||
assert "tom: grensetilfelle() ga ingen tilfeller" in prosess.stdout
|
||||
assert "odelagt: grensetilfelle() kunne ikke kjøres" in prosess.stdout
|
||||
assert "RuntimeError: i stykker" in prosess.stdout
|
||||
assert "Nivå (uten full kjøring, --rask): endret: Utvidelser." in prosess.stdout
|
||||
|
||||
|
||||
@pytest.mark.treg
|
||||
def test_full_kontroll(hjem: Path) -> None:
|
||||
prosess = kjor("kontroll", hjem=hjem)
|
||||
assert prosess.returncode == 0, prosess.stdout + prosess.stderr
|
||||
assert "4. Full kjøring: 22 av 22 datafiler like byte for byte med notatet; utskriften lik." in prosess.stdout
|
||||
assert "\nNivå: bit for bit som notatet.\n" in prosess.stdout
|
||||
Loading…
Add table
Add a link
Reference in a new issue