Document controlled rejection V1 baseline

This commit is contained in:
2026-08-20 13:19:54 +02:00
parent 0d4b426021
commit 8ca62fbd92
5 changed files with 264 additions and 0 deletions
@@ -0,0 +1,82 @@
import copy, json, unittest
from pathlib import Path
from src.meeting_lab.controlled_semantic_derivation.experiment_h import DerivationValidationError
from src.meeting_lab.controlled_semantic_derivation.experiment_rejection_v1 import (
build_target_prompt, derive, load_cases, validate_target,
)
CASES=load_cases(Path("tests/gold/controlled_rejection_v1/cases.json"))
BY_ID={c["case_id"]:c for c in CASES}
def negative(case, form=None):
return {"observation_id":case["expected"]["candidate_observation_id"],"negative_act_form":form or case["expected"]["negative_act_form"],"normalized_action_text":None if (form or case["expected"]["negative_act_form"])=="none" else "semantische Aktion"}
def target(case, oid=None, text="konkrete Zielhandlung"):
return {"candidate_observation_id":case["expected"]["candidate_observation_id"],"target_observation_id":oid or case["expected"]["target_observation_id"],"normalized_target_text":text}
class ControlledRejectionV1Tests(unittest.TestCase):
def test_positive_forms_derive_and_provenance_survives(self):
for cid in ("CR-01","CR-02","CR-07","CR-08"):
c=BY_ID[cid]; out=derive(c["observations"],negative(c),target(c))
self.assertEqual(out["derived_result"]["status"],"explicitly_rejected")
self.assertEqual(out["derived_result"]["support"]["target"]["evidence_id"],"e1")
self.assertNotIn("responsible_person",json.dumps(out["derived_result"]))
def test_noneligible_form_never_derives_even_with_target(self):
for form in ["personal_preference","recommendation","temporary_non_action","none"]:
c=BY_ID["CR-03"]; self.assertIsNone(derive(c["observations"],negative(c,form),target(c))["derived_result"])
def test_missing_target_prevents_derivation(self):
c=BY_ID["CR-02"]; t=target(c); t.update(target_observation_id=None,normalized_target_text=None)
self.assertIsNone(derive(c["observations"],negative(c),t)["derived_result"])
def test_unknown_ids_rejected(self):
for field in ["candidate_observation_id","target_observation_id"]:
c=BY_ID["CR-02"]; t=target(c); t[field]="obs_unknown"
with self.assertRaises(DerivationValidationError): validate_target(t,c["observations"])
def test_target_after_candidate_cannot_derive(self):
c=copy.deepcopy(BY_ID["CR-02"]); n={"observation_id":"obs_1","negative_act_form":"explicit_non_pursuit","normalized_action_text":"x"}; t={"candidate_observation_id":"obs_1","target_observation_id":"obs_2","normalized_target_text":"x"}
self.assertIsNone(derive(c["observations"],n,t)["derived_result"])
def test_same_observation_target_allowed(self):
c=BY_ID["CR-01"]; self.assertIsNotNone(derive(c["observations"],negative(c),target(c))["derived_result"])
def test_duplicate_provenance_rejected(self):
for field in ["observation_id","evidence_id"]:
c=copy.deepcopy(BY_ID["CR-02"]); c["observations"][1][field]=c["observations"][0][field]
with self.assertRaises(DerivationValidationError): derive(c["observations"],negative(BY_ID["CR-02"]),target(BY_ID["CR-02"]))
def test_target_text_constraints(self):
c=BY_ID["CR-02"]
with self.assertRaises(DerivationValidationError): validate_target(target(c,text=""),c["observations"])
t=target(c); t["target_observation_id"]=None
with self.assertRaises(DerivationValidationError): validate_target(t,c["observations"])
def test_null_target_accepts_only_null_text(self):
c=BY_ID["CR-02"]; t=target(c); t.update(target_observation_id=None,normalized_target_text=None)
self.assertEqual(validate_target(t,c["observations"]),t)
def test_forbidden_and_unknown_fields_rejected(self):
for extra in [{"status":"rejected"},{"nested":{"decision":True}},{"extra":1}]:
c=BY_ID["CR-02"]; t=target(c); t.update(extra)
with self.assertRaises(DerivationValidationError): validate_target(t,c["observations"])
def test_candidate_outputs_must_agree(self):
c=BY_ID["CR-02"]; t=target(c); t["candidate_observation_id"]="obs_1"
with self.assertRaises(DerivationValidationError): derive(c["observations"],negative(c),t)
def test_scope_and_alternative_fixture_contract(self):
self.assertLessEqual({"real","Druckversuch"},{x for group in BY_ID["CR-07"]["expected"]["material_concepts"] for x in group})
self.assertEqual(BY_ID["CR-08"]["expected"]["forbidden_concepts"],["Technikum"])
def test_target_prompt_is_semantic_only_and_fixed(self):
prompt=build_target_prompt(BY_ID["CR-08"])
self.assertIn("Ignore any separate positive alternative",prompt)
self.assertIn("Do not classify the negative act",prompt)
def test_accepted_negative_act_inputs_are_exactly_reused(self):
root=Path("artifacts/experiments/negative_act_form_v0/20260820_qwen35_9b_single_run")
for cid,nid in (("CR-01","NA-01"),("CR-03","NA-03"),("CR-04","NA-04"),("CR-05","NA-05"),("CR-06","NA-06")):
accepted=json.loads((root/nid.lower()/"v3_style_input_observations.json").read_text())
self.assertEqual(BY_ID[cid]["observations"],accepted)