Skip to content

Commit fcff01f

Browse files
committed
fix: reject empty InteractionFeedback payloads (CodeRabbit round 3)
Add model_validator requiring at least one rating or non-blank free_text in InteractionFeedback. Prevents empty feedback records with no signal from being stored. Add tests for empty feedback rejection and free-text-only feedback acceptance.
1 parent 0cddc0f commit fcff01f

2 files changed

Lines changed: 42 additions & 1 deletion

File tree

src/synthorg/hr/evaluation/models.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,25 @@ class InteractionFeedback(BaseModel):
137137
description='Origin of the feedback (e.g. "human", "automated")',
138138
)
139139

140+
@model_validator(mode="after")
141+
def _validate_has_signal(self) -> Self:
142+
"""Ensure at least one rating or non-blank free_text is present."""
143+
has_rating = any(
144+
v is not None
145+
for v in (
146+
self.clarity_rating,
147+
self.tone_rating,
148+
self.helpfulness_rating,
149+
self.trust_rating,
150+
self.satisfaction_rating,
151+
)
152+
)
153+
has_text = self.free_text is not None and self.free_text.strip()
154+
if not has_rating and not has_text:
155+
msg = "Feedback must contain at least one rating or non-blank free_text"
156+
raise ValueError(msg)
157+
return self
158+
140159

141160
class ResilienceMetrics(BaseModel):
142161
"""Derived resilience metrics from task execution records.

tests/unit/hr/evaluation/test_models.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,11 @@ def test_frozen(self) -> None:
107107
fb = InteractionFeedback(
108108
agent_id=NotBlankStr("agent-001"),
109109
recorded_at=datetime.now(UTC),
110+
clarity_rating=0.5,
110111
source=NotBlankStr("human"),
111112
)
112113
with pytest.raises(ValidationError):
113-
fb.clarity_rating = 0.5 # type: ignore[misc]
114+
fb.clarity_rating = 0.9 # type: ignore[misc]
114115

115116
@pytest.mark.parametrize(
116117
"field",
@@ -165,15 +166,36 @@ def test_auto_generates_id(self) -> None:
165166
fb1 = InteractionFeedback(
166167
agent_id=NotBlankStr("agent-001"),
167168
recorded_at=datetime.now(UTC),
169+
clarity_rating=0.5,
168170
source=NotBlankStr("human"),
169171
)
170172
fb2 = InteractionFeedback(
171173
agent_id=NotBlankStr("agent-001"),
172174
recorded_at=datetime.now(UTC),
175+
clarity_rating=0.5,
173176
source=NotBlankStr("human"),
174177
)
175178
assert fb1.id != fb2.id
176179

180+
def test_empty_feedback_raises(self) -> None:
181+
"""Feedback with no ratings and no free_text must raise."""
182+
with pytest.raises(ValueError, match="at least one rating"):
183+
InteractionFeedback(
184+
agent_id=NotBlankStr("agent-001"),
185+
recorded_at=datetime.now(UTC),
186+
source=NotBlankStr("human"),
187+
)
188+
189+
def test_free_text_only_feedback_ok(self) -> None:
190+
"""Feedback with only free_text (no ratings) is valid."""
191+
fb = InteractionFeedback(
192+
agent_id=NotBlankStr("agent-001"),
193+
recorded_at=datetime.now(UTC),
194+
source=NotBlankStr("human"),
195+
free_text="Great job!",
196+
)
197+
assert fb.free_text == "Great job!"
198+
177199

178200
# ── ResilienceMetrics ─────────────────────────────────────
179201

0 commit comments

Comments
 (0)