Document controlled rejection V1 baseline
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user