feat: add rules engine (internal/rules) with first two disclosure rules
internal/rules implements Stufe 2 from the core principle: the LLM extracts facts, this deterministic engine judges them against versioned YAML rules. Facts/Condition/Rule/Finding types, a loader that refuses to load on a missing id/version or a duplicate rule id rather than silently skipping a bad file, and Evaluate() matching facts against rules. Two real rules grounded in verified research (see rules/OPEN.md for the open questions that surfaced along the way): - WK-001: no disclosure at all despite consideration (§ 5a Abs. 4 UWG, § 22 Abs. 1 MStV) - WK-004: disclosure present but hidden behind a "mehr anzeigen" cut (§ 5a Abs. 4 UWG, Leitfaden der Medienanstalten, LG Köln 12.05.2026) The two are deliberately disjoint (WK-004 requires disclosure_present= true) so a post with no disclosure at all doesn't double-fire both rules. Golden suite in testdata/golden/ covers both rules plus two clean cases; it's this suite, not the UI, that's the actual asset per CLAUDE.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
11
testdata/golden/organic-no-consideration.json
vendored
Normal file
11
testdata/golden/organic-no-consideration.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "Organischer Post ohne Gegenleistung, keine Kennzeichnung noetig",
|
||||
"facts": {
|
||||
"platform": "instagram",
|
||||
"consideration": "keine",
|
||||
"disclosure_present": false,
|
||||
"disclosure_wording": "",
|
||||
"disclosure_before_cut": false
|
||||
},
|
||||
"expected_findings": []
|
||||
}
|
||||
13
testdata/golden/paid-disclosure-hidden-by-cut.json
vendored
Normal file
13
testdata/golden/paid-disclosure-hidden-by-cut.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "Bezahlter Post, Kennzeichnung vorhanden aber hinter 'mehr anzeigen' versteckt",
|
||||
"facts": {
|
||||
"platform": "instagram",
|
||||
"consideration": "bezahlt",
|
||||
"disclosure_present": true,
|
||||
"disclosure_wording": "Werbung",
|
||||
"disclosure_before_cut": false
|
||||
},
|
||||
"expected_findings": [
|
||||
{ "rule_id": "WK-004", "rule_version": 1, "severity": "hoch" }
|
||||
]
|
||||
}
|
||||
11
testdata/golden/paid-disclosure-visible.json
vendored
Normal file
11
testdata/golden/paid-disclosure-visible.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "Bezahlter Post, Kennzeichnung korrekt vor der Kürzung sichtbar",
|
||||
"facts": {
|
||||
"platform": "tiktok",
|
||||
"consideration": "sachbezug",
|
||||
"disclosure_present": true,
|
||||
"disclosure_wording": "Werbung",
|
||||
"disclosure_before_cut": true
|
||||
},
|
||||
"expected_findings": []
|
||||
}
|
||||
13
testdata/golden/paid-no-disclosure.json
vendored
Normal file
13
testdata/golden/paid-no-disclosure.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "Bezahlter Post ohne jede Kennzeichnung",
|
||||
"facts": {
|
||||
"platform": "instagram",
|
||||
"consideration": "bezahlt",
|
||||
"disclosure_present": false,
|
||||
"disclosure_wording": "",
|
||||
"disclosure_before_cut": false
|
||||
},
|
||||
"expected_findings": [
|
||||
{ "rule_id": "WK-001", "rule_version": 1, "severity": "hoch" }
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user