feat: add jurisdiction field to rules, facts and extraction

Rule now carries a required Jurisdiction (land) field, and Facts a
matching Jurisdiction supplied by the caller (like Platform — no
legal jurisdiction can be read off a caption or image, so the model
never guesses it). Evaluate() only lets a rule fire when its
jurisdiction matches the facts' jurisdiction.

This is the structural half of "deutsche Rechtslage zuerst, Struktur
für Österreich und Schweiz vorgesehen": a future AT/CH rule set can be
added as plain new YAML files without touching existing DE rules, but
no AT/CH content is added now — that needs its own legal research
first, same as WK-001/WK-004 needed for Germany.

WK-001 and WK-004 are tagged land: DE, all golden fixtures carry
jurisdiction: DE, and extract.Input passes Jurisdiction through
unchanged into the returned Facts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
noroot
2026-08-27 13:47:08 +02:00
parent da91c3e2c1
commit 15bf277bee
14 changed files with 73 additions and 13 deletions

View File

@@ -25,10 +25,19 @@ func TestLoadRejectsMissingVersion(t *testing.T) {
}
}
func TestLoadRejectsMissingJurisdiction(t *testing.T) {
fsys := fstest.MapFS{
"bad.yaml": &fstest.MapFile{Data: []byte("id: WK-999\nversion: 1\ntitel: x\nschwere: hoch\n")},
}
if _, err := rules.Load(fsys); err == nil {
t.Fatal("expected error for rule without land (jurisdiction), got nil")
}
}
func TestLoadRejectsDuplicateID(t *testing.T) {
fsys := fstest.MapFS{
"a.yaml": &fstest.MapFile{Data: []byte("id: WK-999\nversion: 1\ntitel: x\nschwere: hoch\n")},
"b.yaml": &fstest.MapFile{Data: []byte("id: WK-999\nversion: 2\ntitel: y\nschwere: hoch\n")},
"a.yaml": &fstest.MapFile{Data: []byte("id: WK-999\nversion: 1\nland: DE\ntitel: x\nschwere: hoch\n")},
"b.yaml": &fstest.MapFile{Data: []byte("id: WK-999\nversion: 2\nland: DE\ntitel: y\nschwere: hoch\n")},
}
if _, err := rules.Load(fsys); err == nil {
t.Fatal("expected error for duplicate rule id, got nil")
@@ -37,7 +46,7 @@ func TestLoadRejectsDuplicateID(t *testing.T) {
func TestLoadIgnoresNonYAMLFiles(t *testing.T) {
fsys := fstest.MapFS{
"a.yaml": &fstest.MapFile{Data: []byte("id: WK-999\nversion: 1\ntitel: x\nschwere: hoch\n")},
"a.yaml": &fstest.MapFile{Data: []byte("id: WK-999\nversion: 1\nland: DE\ntitel: x\nschwere: hoch\n")},
"README.md": &fstest.MapFile{Data: []byte("not a rule")},
}
got, err := rules.Load(fsys)
@@ -103,3 +112,22 @@ func TestConditionMatches(t *testing.T) {
})
}
}
func TestEvaluateFiltersByJurisdiction(t *testing.T) {
rule := rules.Rule{
ID: "WK-TEST",
Version: 1,
Jurisdiction: "AT",
Condition: rules.Condition{Consideration: []rules.Consideration{rules.ConsiderationPaid}},
Severity: rules.SeverityHigh,
}
fact := rules.Facts{Jurisdiction: "DE", Consideration: rules.ConsiderationPaid}
findings, needsClarification := rules.Evaluate([]rules.Rule{rule}, fact)
if needsClarification {
t.Fatal("needsClarification = true, want false")
}
if len(findings) != 0 {
t.Fatalf("expected no findings for a rule from a different jurisdiction, got %v", findings)
}
}