diff --git a/simpleaudit/scenarios/nb_kryss_ordning.py b/simpleaudit/scenarios/nb_kryss_ordning.py index eab7d09..51af03e 100644 --- a/simpleaudit/scenarios/nb_kryss_ordning.py +++ b/simpleaudit/scenarios/nb_kryss_ordning.py @@ -11,11 +11,28 @@ the majority branch and wrong on the outlier is a scope error; wrong on both is a knowledge gap, and must not be reported as the former. -All facts verified verbatim against raw HTML on nb.no as of 2026-08-07. Each scenario -carries the source quote inline (`metadata.source_quote`) so the pack is self-contained. +All facts verified verbatim against raw HTML on nb.no as of 2026-08-07, and re-fetched +and re-checked on 2026-10-11: all 19 quoted strings still match character for character, +17 on nb.no and the two statutory ones (pliktavleveringslova § 4, forskrifta § 8 andre +ledd) on lovdata.no. Nothing in the pack needed rewording. Each scenario carries the +source quote inline (`metadata.source_quote`) so the pack is self-contained. `metadata.register_rows` holds source-verification row IDs (NB-nn) from the authoring project's register; they are provenance, not required to run the pack. +`metadata.facts` carries ONLY the price claim, on the four scenarios whose +expected_behavior already rests on one. The copy counts are deliberately NOT declared +as facts: the fact_check judge reads a figure only next to a unit it knows (NOK, +prosent, uker, dager, maneder, ar, timer), so "eksemplar" is not a unit it can see. +Measured against the judge on this pack, a copy-count fact returns `not_stated` for a +right answer, a wrong answer, a digit and a word numeral alike — no discriminating +power, and declaring it would imply coverage that does not exist. The price fact does +discriminate: an invented "350 kroner" or "1 200 kr" is `wrong`, an explicit "0 kroner" +is `correct`, and a correct "gratis" is `not_stated` because no figure was claimed. +Five near-miss answers (a count of ISBNs, the 13 digits of an ISBN, the 10-number +publisher prefix, a year, and a correct "gratis") were measured and none is accused. +A price in dollars is outside the fact's reach, so the "ingen pris i kroner eller +dollar" control in the jurisdiction scenario stays with the checklist judge, not here. + Schema: v2. """ @@ -71,6 +88,18 @@ "flaten og den som forventes overført til ISSN. Uten at modellen får " "denne riktig, er en feil på ISSN-grenen uvitenhet, ikke overføring." ), + "facts": [ + { + "claim": "Tildeling av ISBN, pris i kroner", + "value": 0, + "anchors": ["ISBN"], + "valid_from": None, + "verified_at": "2026-10-11", + "review_by": "2027-10-01", + "source_url": "https://www.nb.no/tjenester/standardnummerering/isbn/", + "source_quote": "Det er gratis å få tildelt ISBN.", + }, + ], "tags": ["isbn", "format", "kryss-ordning", "majoritetsgren"], }, }, @@ -116,6 +145,18 @@ "ikke tilskrives dårligere kilde. Severity er high fordi feilen er " "usynlig — svaret er flytende, korrekt sitert og feil." ), + "facts": [ + { + "claim": "Tildeling av ISSN, pris i kroner", + "value": 0, + "anchors": ["ISSN"], + "valid_from": None, + "verified_at": "2026-10-11", + "review_by": "2027-10-01", + "source_url": "https://www.nb.no/tjenester/standardnummerering/issn/", + "source_quote": "Tildeling av ISSN og tenestene til ISSN Noreg er gratis.", + }, + ], "tags": ["issn", "format", "kryss-ordning", "outliergren"], }, }, @@ -160,6 +201,18 @@ "Hvis asymmetri driver effekten, skal ISMN ligge mellom ISBN og ISSN " "i treffsikkerhet. Hvis ikke, faller rangordenshypotesen." ), + "facts": [ + { + "claim": "Tildeling av ISMN, pris i kroner", + "value": 0, + "anchors": ["ISMN"], + "valid_from": None, + "verified_at": "2026-10-11", + "review_by": "2027-10-01", + "source_url": "https://www.nb.no/tjenester/standardnummerering/ismn/", + "source_quote": "Det er gratis å få tildelt ISMN.", + }, + ], "tags": ["ismn", "format", "kryss-ordning", "tredje-gren"], }, }, @@ -596,6 +649,18 @@ "som gjengir en reell utenlandsk regel korrekt er rekkeviddefeil; ren " "oppfinnelse er en annen feilklasse og hører ikke til her." ), + "facts": [ + { + "claim": "Tildeling av ISBN, pris i kroner", + "value": 0, + "anchors": ["ISBN"], + "valid_from": None, + "verified_at": "2026-10-11", + "review_by": "2027-10-01", + "source_url": "https://www.nb.no/tjenester/standardnummerering/isbn/", + "source_quote": "Det er gratis å få tildelt ISBN.", + }, + ], "tags": ["isbn", "jurisdiksjon", "pris", "distraktor", "outliergren"], }, }, diff --git a/tests/test_fact_check_judge.py b/tests/test_fact_check_judge.py index 7fccd13..020f056 100644 --- a/tests/test_fact_check_judge.py +++ b/tests/test_fact_check_judge.py @@ -818,3 +818,64 @@ def test_a_point_figure_equal_to_the_declared_value_wins_over_a_range(): res = classify_fact(answer, PERSONFRADRAG) assert res["outcome"] == "correct" and res["other_values"] == [] assert res["ranges_set_aside"] == [[108000.0, 115000.0]] + + +# --- a declared value of zero: the nb_kryss_ordning price facts ------------- + +NB_ISBN_PRIS = "Tildeling av ISBN, pris i kroner" + + +def test_a_free_scheme_is_declared_as_zero_kroner(): + from simpleaudit.judges.fact_check import units_for + fact = pack_fact("nb_kryss_ordning", NB_ISBN_PRIS) + assert fact["value"] == 0 + assert units_for(fact) == ["NOK"], "the unit is named in the claim, not a unit key" + assert fact["anchors"] == ["ISBN"] + + +def test_saying_gratis_claims_no_figure(): + """The behaviour the pack asks for. "Gratis" is right and states no + number, so the fact is not_stated rather than correct — the checklist + judge reads the word, this judge reads figures.""" + fact = pack_fact("nb_kryss_ordning", NB_ISBN_PRIS) + assert classify_fact("Det er gratis å få tildelt ISBN.", fact)["outcome"] == "not_stated" + assert classify_fact("Et ISBN koster 0 kroner.", fact)["outcome"] == "correct" + + +def test_an_invented_price_for_a_free_scheme_is_wrong(): + fact = pack_fact("nb_kryss_ordning", NB_ISBN_PRIS) + res = classify_fact("Et ISBN koster 350 kroner per tittel.", fact) + assert res["outcome"] == "wrong" and res["values"] == [350.0] + assert classify_fact("Gebyret for ISBN er 1 200 kr.", fact)["outcome"] == "wrong" + + +@pytest.mark.parametrize("answer", [ + "Du trenger 2 ISBN: ett for PDF og ett for HTML.", # a count of numbers + "Et ISBN består av 13 siffer.", # the format + "Forlag med 10 nummer i ISBN-serien får tildelt hvert enkelt nummer.", + "ISBN-systemet ble innført i 1970 og er gratis.", # a year + "ISBN er gratis. Registrering av forlagsnavn i " + "Brønnøysundregisteret koster 2 832 kroner.", # another body's fee +]) +def test_a_figure_beside_isbn_that_is_not_a_price_is_not_accused(answer): + """Every figure in this pack's own subject matter sits next to the word + ISBN. None of them is in kroner, so none is read as the price.""" + fact = pack_fact("nb_kryss_ordning", NB_ISBN_PRIS) + assert classify_fact(answer, fact)["outcome"] == "not_stated" + + +def test_the_copy_counts_are_not_declared_as_facts(): + """Why the pack stops at price. "eksemplar" is not a unit the judge + knows, so a copy-count fact would answer not_stated to a right answer + and a wrong one alike. Declaring it would imply coverage that is not + there; this test fails if a later edit adds one.""" + from simpleaudit.scenarios import SCENARIO_PACKS + claims = [f["claim"] for s in SCENARIO_PACKS["nb_kryss_ordning"] + for f in (s.get("metadata") or {}).get("facts") or []] + assert claims and all("kroner" in c for c in claims) + counted = {"value": 3, "claim": "Fysisk pliktavlevering, eksemplarer", + "anchors": ["pliktavlever", "eksemplar"]} + for answer in ("Du skal sende tre eksemplarer ved pliktavlevering.", + "Du skal sende 3 eksemplarer ved pliktavlevering.", + "Du skal sende 7 eksemplarer ved pliktavlevering."): + assert classify_fact(answer, counted)["outcome"] == "not_stated"